diff --git a/go.mod b/go.mod index a6d989a..55fdca2 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-20230402160147-f08f645432c6 + forge.cadoles.com/arcad/edge v0.0.0-20230405131922-006f13bc7b88 github.com/Masterminds/sprig/v3 v3.2.3 github.com/alecthomas/participle/v2 v2.0.0-beta.5 github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 diff --git a/go.sum b/go.sum index 5e17670..93de92b 100644 --- a/go.sum +++ b/go.sum @@ -58,6 +58,8 @@ forge.cadoles.com/arcad/edge v0.0.0-20230328183829-d8ce2901d2ab h1:xOtzLAYOUcKd/ forge.cadoles.com/arcad/edge v0.0.0-20230328183829-d8ce2901d2ab/go.mod h1:ONd6vyQ0IM0vHi1i+bmZBRc1Fd0BoXMuDdY/+0sZefw= forge.cadoles.com/arcad/edge v0.0.0-20230402160147-f08f645432c6 h1:MxMEBSEvwagUrFORUJ9snZekFIKkaV3OB0EplXra+LU= forge.cadoles.com/arcad/edge v0.0.0-20230402160147-f08f645432c6/go.mod h1:ONd6vyQ0IM0vHi1i+bmZBRc1Fd0BoXMuDdY/+0sZefw= +forge.cadoles.com/arcad/edge v0.0.0-20230405131922-006f13bc7b88 h1:d+AXw/Kx8X7M4GvtPC/mkZWK3tRMT051XORAiQAnClA= +forge.cadoles.com/arcad/edge v0.0.0-20230405131922-006f13bc7b88/go.mod h1:Vx4iq/oewXUOkGyi8QKc14clTLNO1sWpb0SjBYELlAs= 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= diff --git a/internal/agent/controller/app/app_handler.go b/internal/agent/controller/app/app_handler.go index 24b2e47..a1d5cda 100644 --- a/internal/agent/controller/app/app_handler.go +++ b/internal/agent/controller/app/app_handler.go @@ -4,8 +4,8 @@ import ( "bytes" "context" "database/sql" + "net" "path/filepath" - "sync" "text/template" "forge.cadoles.com/Cadoles/emissary/internal/agent/controller/app/spec" @@ -21,12 +21,13 @@ import ( "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" - "forge.cadoles.com/arcad/edge/pkg/module/net" + netModule "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" + "gitlab.com/wpetit/goweb/logger" ) const defaultSQLiteParams = "?_pragma=foreign_keys(1)&_pragma=journal_mode(WAL)&_txlock=immediate" @@ -107,46 +108,132 @@ func getAuthKeySet(config *spec.Config) (jwk.Set, error) { return keySet, nil } -func createGetAppURL(specs *spec.Spec) GetURLFunc { - var ( - compileOnce sync.Once - urlTemplate *template.Template - err error - ) +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 + } - return func(ctx context.Context, manifest *app.Manifest) (string, error) { + 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), + ) + } + + 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 + } + + 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) } - 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 + 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(bus bus.Bus, db *sql.DB, spec *appSpec.Spec, keySet jwk.Set) []app.ServerModuleFactory { @@ -158,7 +245,7 @@ func (c *Controller) getAppModules(bus bus.Bus, db *sql.DB, spec *appSpec.Spec, module.ConsoleModuleFactory(), cast.CastModuleFactory(), module.LifecycleModuleFactory(), - net.ModuleFactory(bus), + netModule.ModuleFactory(bus), module.RPCModuleFactory(bus), module.StoreModuleFactory(ds), blob.ModuleFactory(bus, bs), diff --git a/internal/agent/controller/app/app_handler_test.go b/internal/agent/controller/app/app_handler_test.go new file mode 100644 index 0000000..cef77aa --- /dev/null +++ b/internal/agent/controller/app/app_handler_test.go @@ -0,0 +1,66 @@ +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", + }, + }, + Config: &spec.Config{ + AppURLResolving: &spec.AppURLResolving{ + IfaceMappings: map[string]string{ + "lo": "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) + } +} diff --git a/internal/agent/controller/app/app_repository.go b/internal/agent/controller/app/app_repository.go index a41444c..d81980a 100644 --- a/internal/agent/controller/app/app_repository.go +++ b/internal/agent/controller/app/app_repository.go @@ -11,12 +11,12 @@ import ( "gitlab.com/wpetit/goweb/logger" ) -type GetURLFunc func(context.Context, *app.Manifest) (string, error) +type ResolveAppURLFunc func(context.Context, *app.Manifest, string) (string, error) type AppRepository struct { - getURL GetURLFunc - bundles []string - mutex sync.RWMutex + resolveAppURL ResolveAppURLFunc + bundles []string + mutex sync.RWMutex } // Get implements app.Repository @@ -33,7 +33,7 @@ func (r *AppRepository) Get(ctx context.Context, id app.ID) (*app.Manifest, erro } // GetURL implements app.Repository -func (r *AppRepository) GetURL(ctx context.Context, id app.ID) (string, error) { +func (r *AppRepository) GetURL(ctx context.Context, id app.ID, from string) (string, error) { r.mutex.RLock() defer r.mutex.RUnlock() @@ -42,7 +42,7 @@ func (r *AppRepository) GetURL(ctx context.Context, id app.ID) (string, error) { return "", errors.WithStack(err) } - url, err := r.getURL(ctx, manifest) + url, err := r.resolveAppURL(ctx, manifest, from) if err != nil { return "", errors.WithStack(err) } @@ -80,11 +80,11 @@ func (r *AppRepository) List(ctx context.Context) ([]*app.Manifest, error) { return manifests, nil } -func (r *AppRepository) Update(getURL GetURLFunc, bundles []string) { +func (r *AppRepository) Update(resolveAppURL ResolveAppURLFunc, bundles []string) { r.mutex.Lock() defer r.mutex.Unlock() - r.getURL = getURL + r.resolveAppURL = resolveAppURL r.bundles = bundles } @@ -118,7 +118,7 @@ func (r *AppRepository) findManifest(ctx context.Context, id app.ID) (*app.Manif func NewAppRepository() *AppRepository { return &AppRepository{ - getURL: func(ctx context.Context, m *app.Manifest) (string, error) { + resolveAppURL: func(ctx context.Context, m *app.Manifest, from string) (string, error) { return "", errors.New("unavailable") }, bundles: []string{}, diff --git a/internal/agent/controller/app/controller.go b/internal/agent/controller/app/controller.go index 860414b..72e26db 100644 --- a/internal/agent/controller/app/controller.go +++ b/internal/agent/controller/app/controller.go @@ -96,7 +96,14 @@ func (c *Controller) updateApps(ctx context.Context, specs *spec.Spec) { } } - c.updateAppRepository(ctx, specs) + if err := c.updateAppRepository(ctx, specs); err != nil { + logger.Error( + ctx, "could not update app repository", + logger.E(errors.WithStack(err)), + ) + + return + } // (Re)start apps if necessary for appKey := range specs.Apps { @@ -109,32 +116,32 @@ func (c *Controller) updateApps(ctx context.Context, specs *spec.Spec) { } } -func (c *Controller) updateAppRepository(ctx context.Context, specs *spec.Spec) { +func (c *Controller) updateAppRepository(ctx context.Context, specs *spec.Spec) error { bundles := make([]string, 0, len(specs.Apps)) for appKey, app := range specs.Apps { path := c.getAppBundlePath(appKey, app.Format) bundles = append(bundles, path) } - getURL := createGetAppURL(specs) + resolveAppURL, err := createResolveAppURL(specs) + if err != nil { + return errors.WithStack(err) + } - c.appRepository.Update(getURL, bundles) + 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] - var auth *spec.Auth - if specs.Config != nil { - auth = specs.Config.Auth - } - appDef := struct { - App spec.AppEntry - Auth *spec.Auth + App spec.AppEntry + Config *spec.Config }{ - App: appEntry, - Auth: auth, + App: appEntry, + Config: specs.Config, } newAppDefHash, err := hashstructure.Hash(appDef, hashstructure.FormatV2, nil) @@ -164,27 +171,30 @@ func (c *Controller) updateApp(ctx context.Context, specs *spec.Spec, appKey str server = nil } - if server == nil { + newServerEntry := func() (*serverEntry, error) { options, err := c.getHandlerOptions(ctx, appKey, specs) if err != nil { - return errors.Wrap(err, "could not create handler options") - } - - var auth *spec.Auth - if specs.Config != nil { - auth = specs.Config.Auth + return nil, errors.Wrap(err, "could not create handler options") } server = &serverEntry{ - Server: NewServer(bundle, auth, options...), + Server: NewServer(bundle, specs.Config, options...), AppDefHash: 0, } - c.servers[appKey] = server + return server, nil + } + + if server == nil { + serverEntry, err := newServerEntry() + if err != nil { + return errors.WithStack(err) + } + + c.servers[appKey] = serverEntry } defChanged := newAppDefHash != server.AppDefHash - if server.Server.Running() && !defChanged { return nil } @@ -194,6 +204,17 @@ func (c *Controller) updateApp(ctx context.Context, specs *spec.Spec, appKey str 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", diff --git a/internal/agent/controller/app/server.go b/internal/agent/controller/app/server.go index 248ad72..23fa256 100644 --- a/internal/agent/controller/app/server.go +++ b/internal/agent/controller/app/server.go @@ -2,6 +2,7 @@ package app import ( "context" + "net" "net/http" "strings" "sync" @@ -31,7 +32,7 @@ type Server struct { handlerOptions []edgeHTTP.HandlerOptionFunc server *http.Server serverMutex sync.RWMutex - auth *appSpec.Auth + config *appSpec.Config } func (s *Server) Start(ctx context.Context, addr string) (err error) { @@ -53,8 +54,19 @@ func (s *Server) Start(ctx context.Context, addr string) (err error) { return errors.Wrap(err, "could not load app bundle") } - if err := s.configureAuth(router, s.auth); err != nil { - return errors.WithStack(err) + if s.config != nil { + if s.config.UnexpectedHostRedirect != nil { + router.Use(unexpectedHostRedirect( + s.config.UnexpectedHostRedirect.HostTarget, + s.config.UnexpectedHostRedirect.AcceptedHostPatterns..., + )) + } + + if s.config.Auth != nil { + if err := s.configureAuth(router, s.config.Auth); err != nil { + return errors.WithStack(err) + } + } } router.Handle("/*", handler) @@ -124,13 +136,9 @@ func (s *Server) Stop() error { } 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 + var rawKey any = auth.Local.Key if strKey, ok := rawKey.(string); ok { rawKey = []byte(strKey) } @@ -141,54 +149,75 @@ func (s *Server) configureAuth(router chi.Router, auth *spec.Auth) error { } cookieDuration := defaultCookieDuration - if s.auth.Local.CookieDuration != "" { - cookieDuration, err = time.ParseDuration(s.auth.Local.CookieDuration) + if auth.Local.CookieDuration != "" { + cookieDuration, err = time.ParseDuration(auth.Local.CookieDuration) if err != nil { return errors.WithStack(err) } } - if s.auth.Local.CookieDomain != "" { - router.Use(invalidCookieDomainRedirect(s.auth.Local.CookieDomain)) - } - router.Handle("/auth/*", authHTTP.NewLocalHandler( jwa.HS256, key, authHTTP.WithRoutePrefix("/auth"), - authHTTP.WithAccounts(s.auth.Local.Accounts...), - authHTTP.WithCookieOptions(s.auth.Local.CookieDomain, cookieDuration), + authHTTP.WithAccounts(auth.Local.Accounts...), + authHTTP.WithCookieOptions(getCookieDomain, cookieDuration), )) } return nil } -func NewServer(bundle bundle.Bundle, auth *appSpec.Auth, handlerOptions ...edgeHTTP.HandlerOptionFunc) *Server { +func NewServer(bundle bundle.Bundle, config *spec.Config, handlerOptions ...edgeHTTP.HandlerOptionFunc) *Server { return &Server{ bundle: bundle, - auth: auth, + config: config, handlerOptions: handlerOptions, } } -func invalidCookieDomainRedirect(cookieDomain string) func(http.Handler) http.Handler { - domain := strings.TrimPrefix(cookieDomain, ".") - hostPattern := "*" + domain +func getCookieDomain(r *http.Request) (string, error) { + host, _, err := net.SplitHostPort(r.Host) + if err != nil { + return "", errors.WithStack(err) + } + // If host is an IP address + if wildcard.Match(host, "*.*.*.*") { + 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) { - hostParts := strings.SplitN(r.Host, ":", 2) + host, port, err := net.SplitHostPort(r.Host) + if err != nil { + logger.Error(r.Context(), "could not split host/port", logger.E(errors.WithStack(err))) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - if !wildcard.Match(hostParts[0], hostPattern) { + return + } + + matched := wildcard.MatchAny(host, acceptedHostPatterns...) + + if !matched { url := r.URL - newHost := domain - if len(hostParts) > 1 { - newHost += ":" + hostParts[1] + url.Host = hostTarget + if port != "" { + url.Host += ":" + port } - url.Host = newHost - http.Redirect(w, r, url.String(), http.StatusTemporaryRedirect) return diff --git a/internal/agent/controller/app/spec/schema.json b/internal/agent/controller/app/spec/schema.json index 7c6a6c3..ad769a6 100644 --- a/internal/agent/controller/app/spec/schema.json +++ b/internal/agent/controller/app/spec/schema.json @@ -38,57 +38,94 @@ } } }, - "auth": { + "config": { "type": "object", "properties": { - "local": { + "appUrlResolving": { "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" - ] + "ifaceMappings": { + "type": "object", + "patternProperties": { + ".*": { + "type": "string" + } } }, - "cookieDomain": { - "type": "string" - }, - "cookieDuration": { + "defaultUrlTemplate": { "type": "string" } }, - "required": [ - "key" - ] + "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 } - } - }, - "config": { - "appUrlTemplate": { - "type": "string" - } + }, + "additionalProperties": false } }, "required": [ diff --git a/internal/agent/controller/app/spec/spec.go b/internal/agent/controller/app/spec/spec.go index 1179fa3..7b1b258 100644 --- a/internal/agent/controller/app/spec/spec.go +++ b/internal/agent/controller/app/spec/spec.go @@ -32,8 +32,19 @@ type LocalAuth struct { } type Config struct { - Auth *Auth `json:"auth"` - AppURLTemplate string `json:"appUrlTemplate"` + 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 { diff --git a/internal/agent/controller/app/spec/testdata/spec-ok.json b/internal/agent/controller/app/spec/testdata/spec-ok.json index a2b5633..c1cd95f 100644 --- a/internal/agent/controller/app/spec/testdata/spec-ok.json +++ b/internal/agent/controller/app/spec/testdata/spec-ok.json @@ -9,32 +9,44 @@ "format": "zip" } }, - "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" + "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" } } }, diff --git a/misc/spec-samples/app.emissary.cadoles.com.json b/misc/spec-samples/app.emissary.cadoles.com.json index 16b6d78..06d521b 100644 --- a/misc/spec-samples/app.emissary.cadoles.com.json +++ b/misc/spec-samples/app.emissary.cadoles.com.json @@ -1,36 +1,46 @@ { "apps": { - "portal": { - "url": "https://emissary.cadol.es/files/apps/arcad.portal_v2023.3.28-3feda80.zip", - "sha256sum": "921402c44a5fa554d5b630d1284957b05416aa6872b402314cf52e964e06fac5", - "address": "127.0.0.1:8082", + "edge.portal": { + "url": "https://emissary.cadol.es/files/apps/edge.portal_v2023.4.5-45546c4.zip", + "sha256sum": "c83e7e4b3785f5f4d3fcae7cad334819626015b11b446520aa79f42176a2744d", + "address": ":8082", "format": "zip" }, - "hextris": { + "app.arcad.edge.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", + "address": ":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", + "edge.sdk.client.test": { + "url": "https://emissary.cadol.es/files/apps/edge.sdk.client.test_v2023.4.2-f08f645.zip", + "sha256sum": "8b48388c817802ebeb38907b3a42f1189dc0759f94c5f33de4546c1a7ebfc784", + "address": ":8084", "format": "zip" }, - "diffusion": { - "url": "https://emissary.cadol.es/files/apps/arcad.diffusion_v2023.3.29-5b3fab4.zip", - "sha256sum": "1282e75719beedbc7c7e67879389d0f3e11c86d3d2c37cf13da624a66faaeb58", - "address": "127.0.0.1:8085", + "arcad.diffusion": { + "url": "https://emissary.cadol.es/files/apps/arcad.diffusion_v2023.4.5-ffcd1c7.zip", + "sha256sum": "a51a961212470ce1de4527aaaec9e8e0286a978ec675ff9df29b2029daf05a55", + "address": ":8085", "format": "zip" } }, "config": { - "appUrlTemplate": "http://{{ last ( splitList \".\" ( toString .Manifest.ID ) ) }}.arcad.local:8080", + "appUrlResolving": { + "ifaceMappings": { + "lo": "http://{{ .DeviceIP }}:{{ .AppPort }}", + "wlp4s0": "http://{{ .DeviceIP }}:{{ .AppPort }}", + "enp0s31f6": "http://{{ .DeviceIP }}:{{ .AppPort }}" + }, + "defaultUrlTemplate": "http://{{ last ( splitList \".\" ( toString .Manifest.ID ) ) }}.localhost.arcad.lan:8080" + }, + "unexpectedHostRedirect": { + "acceptedHostPatterns": ["arcad.lan", "*.arcad.lan", "arcad-*.local", "*.*.*.*"], + "hostTarget": "localhost.arcad.lan" + }, "auth": { "local": { "key": "absolutlynotsecret", - "cookieDomain": ".arcad.local", "cookieDuration": "1h", "accounts": [ { diff --git a/misc/spec-samples/mdns.emissary.cadoles.com.json b/misc/spec-samples/mdns.emissary.cadoles.com.json index 97e3df0..ca5470b 100644 --- a/misc/spec-samples/mdns.emissary.cadoles.com.json +++ b/misc/spec-samples/mdns.emissary.cadoles.com.json @@ -3,36 +3,27 @@ "arcad": { "type": "_http._tcp", "port": 8080, - "host": "arcad", - "ifaces": ["wlp4s0"] + "host": "arcad" }, "portal": { "type": "_http._tcp", "port": 8080, - "host": "portal", - "domain": "arcad.local", - "ifaces": ["wlp4s0"] + "host": "arcad-portal" }, "hextris": { "type": "_http._tcp", "port": 8080, - "host": "hextris", - "domain": "arcad.local", - "ifaces": ["wlp4s0"] + "host": "arcad-hextris" }, "test": { "type": "_http._tcp", "port": 8080, - "host": "test", - "domain": "arcad.local", - "ifaces": ["wlp4s0"] + "host": "arcad-test" }, "diffusion": { "type": "_http._tcp", "port": 8080, - "host": "diffusion", - "domain": "arcad.local", - "ifaces": ["wlp4s0"] + "host": "arcad-diffusion" } } } \ No newline at end of file diff --git a/misc/spec-samples/proxy.emissary.cadoles.com.json b/misc/spec-samples/proxy.emissary.cadoles.com.json index 95310a3..4975139 100644 --- a/misc/spec-samples/proxy.emissary.cadoles.com.json +++ b/misc/spec-samples/proxy.emissary.cadoles.com.json @@ -4,19 +4,19 @@ "address": ":8080", "mappings": [ { - "hostPattern": "portal.arcad.local:*", + "hostPattern": "portal.localhost.arcad.lan:*", "target": "http://localhost:8082" }, { - "hostPattern": "hextris.arcad.local:*", + "hostPattern": "hextris.localhost.arcad.lan:*", "target": "http://localhost:8083" }, { - "hostPattern": "test.arcad.local:*", + "hostPattern": "test.localhost.arcad.lan:*", "target": "http://localhost:8084" }, { - "hostPattern": "diffusion.arcad.local:*", + "hostPattern": "diffusion.localhost.arcad.lan:*", "target": "http://localhost:8085" }, {