Require access to Still via method

This commit adds a safeguard to accessing the still from a specific
component by requiring access via the component.GetStill method.
This commit is contained in:
Tom Wiesing 2024-04-08 22:39:32 +02:00
parent 81fa84c244
commit 8235ea9105
No known key found for this signature in database
63 changed files with 288 additions and 197 deletions

View file

@ -36,8 +36,9 @@ type AuthInfo struct {
}
func (a *API) HandleRoute(ctx context.Context, path string) (http.Handler, error) {
return &Handler[AuthInfo]{
Config: a.Config,
Config: component.GetStill(a).Config,
Auth: a.dependencies.Auth,
Methods: []string{"GET"},

View file

@ -58,7 +58,7 @@ func (next *Next) getInstance(r *http.Request) (wisski *wisski.WissKI, path stri
}
// find the slug
slug, ok := next.Config.HTTP.SlugFromHost(url.Host)
slug, ok := component.GetStill(next).Config.HTTP.SlugFromHost(url.Host)
if slug == "" || !ok {
return nil, "", httpx.ErrBadRequest
}

View file

@ -5,6 +5,7 @@ import (
"errors"
"net/http"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/assets"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating"
@ -64,9 +65,10 @@ func (panel *UserPanel) sshRoute(ctx context.Context) http.Handler {
return sc, err
}
sc.Domain = panel.Config.HTTP.PrimaryDomain
sc.PanelDomain = panel.Config.HTTP.PanelDomain()
sc.Port = panel.Config.Listen.SSHPort
config := component.GetStill(panel).Config
sc.Domain = config.HTTP.PrimaryDomain
sc.PanelDomain = config.HTTP.PanelDomain()
sc.Port = config.Listen.SSHPort
// pick the first domain that the user has access to as an example
grants, err := panel.dependencies.Policy.User(r.Context(), user.User.User)
@ -75,7 +77,7 @@ func (panel *UserPanel) sshRoute(ctx context.Context) http.Handler {
} else {
sc.Slug = "example"
}
sc.Hostname = panel.Config.HTTP.HostFromSlug(sc.Slug)
sc.Hostname = config.HTTP.HostFromSlug(sc.Slug)
sc.Keys, err = panel.dependencies.Keys.Keys(r.Context(), user.User.User)
if err != nil {

View file

@ -5,6 +5,7 @@ import (
"html/template"
"net/http"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/assets"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating"
@ -52,7 +53,7 @@ func (panel *UserPanel) tokensRoute(ctx context.Context) http.Handler {
return tc, err
}
tc.Domain = template.URL(panel.Config.HTTP.JoinPath().String())
tc.Domain = template.URL(component.GetStill(panel).Config.HTTP.JoinPath().String())
// get the tokens
tc.Tokens, err = panel.dependencies.Tokens.Tokens(r.Context(), user.User.User)
@ -178,7 +179,7 @@ func (panel *UserPanel) tokensAddRoute(ctx context.Context) http.Handler {
// render the created context
return panel.dependencies.Handling.WriteHTML(
tplDone.Context(r, TokenCreateContext{
Domain: template.URL(panel.Config.HTTP.JoinPath().String()),
Domain: template.URL(component.GetStill(panel).Config.HTTP.JoinPath().String()),
Token: tok,
}),
nil,

View file

@ -46,8 +46,7 @@ func (panel *UserPanel) routeUser(ctx context.Context) http.Handler {
menuTOTPAction,
menuSSH,
}
if panel.Config.HTTP.API.Value {
if component.GetStill(panel).Config.HTTP.API.Value {
actions = append(actions, menuTokens)
}

View file

@ -107,7 +107,7 @@ func (auth *Auth) checkUser(ctx context.Context, name string) (user *AuthUser, e
// If the session is not set, creates a new session.
func (auth *Auth) session(r *http.Request) (*sessions.Session, error) {
return auth.store.Get(func() sessions.Store {
return sessions.NewCookieStore([]byte(auth.Config.SessionSecret))
return sessions.NewCookieStore([]byte(component.GetStill(auth).Config.SessionSecret))
}).Get(r, server.SessionCookie)
}

View file

@ -18,7 +18,7 @@ var (
)
func (binder *Binder) Path() string {
return filepath.Join(binder.Still.Config.Paths.Root, "core", "binder")
return filepath.Join(component.GetStill(binder).Config.Paths.Root, "core", "binder")
}
func (binder *Binder) Context(parent component.InstallationContext) component.InstallationContext {
@ -29,17 +29,20 @@ func (binder *Binder) Context(parent component.InstallationContext) component.In
var composeTemplate embed.FS
func (binder *Binder) Stack() component.StackWithResources {
config := component.GetStill(binder).Config
return component.MakeStack(binder, component.StackWithResources{
ContextPath: ".",
Resources: composeTemplate,
ComposerYML: func(root *yaml.Node) (*yaml.Node, error) {
ports := binder.Config.Listen.ComposePorts("8000")
ports := config.Listen.ComposePorts("8000")
if err := yamlx.ReplaceWith(root, ports, "services", "binder", "ports"); err != nil {
return nil, err
}
command := binder.Config.HTTP.TCPMuxCommand("0.0.0.0:8000", "http:80", "http:443", "ssh:2222")
command := config.HTTP.TCPMuxCommand("0.0.0.0:8000", "http:80", "http:443", "ssh:2222")
if err := yamlx.ReplaceWith(root, command, "services", "binder", "command"); err != nil {
return nil, err
}
@ -48,7 +51,7 @@ func (binder *Binder) Stack() component.StackWithResources {
},
EnvContext: map[string]string{
"DOCKER_NETWORK_NAME": binder.Config.Docker.Network(),
"DOCKER_NETWORK_NAME": config.Docker.Network(),
},
})
}

View file

@ -33,7 +33,7 @@ type Component interface {
// Base is embedded into every Component
type Base struct {
name, id string // name and id of this component
Still // the underlying still of the distillery
still Still // the underlying still of the distillery
}
//lint:ignore U1000 used to implement the private methods of [Component]
@ -45,7 +45,7 @@ func (cb *Base) getBase() *Base {
// Init is only initended to be used within a lifetime.Lifetime[Component,Still].
func Init(component Component, core Still) {
base := component.getBase() // pointer to a struct
base.Still = core
base.still = core
tp := reflect.TypeOf(component).Elem()
base.name = strings.ToLower(tp.Name())
@ -60,8 +60,13 @@ func (cb Base) ID() string {
return cb.id
}
// GetStill returns the still underlying the provided component.
func GetStill(c Component) Still {
return c.getBase().still
}
// Still represents the central part of a distillery.
// It is used inside the main distillery struct, as well as every component via [ComponentBase].
// It holds configuration of the distillery.
type Still struct {
Config *config.Config // the configuration of the distillery
Upstream Upstream

View file

@ -33,7 +33,7 @@ type Exporter struct {
// Path returns the path that contains all snapshot related data.
func (dis *Exporter) Path() string {
return filepath.Join(dis.Config.Paths.Root, "snapshots")
return filepath.Join(component.GetStill(dis).Config.Paths.Root, "snapshots")
}
// StagingPath returns the path to the directory containing a temporary staging area for snapshots.

View file

@ -36,10 +36,11 @@ func (control *Config) Backup(scontext *component.StagingContext) error {
// backupfiles lists the files to be backed up.
func (control *Config) backupFiles() []string {
config := component.GetStill(control).Config
return []string{
control.Config.ConfigPath,
control.Config.Paths.ExecutablePath(),
control.Config.Paths.OverridesJSON,
control.Config.Paths.ResolverBlocks,
config.ConfigPath,
config.Paths.ExecutablePath(),
config.Paths.OverridesJSON,
config.Paths.ResolverBlocks,
}
}

View file

@ -7,12 +7,14 @@ import (
"os"
"path/filepath"
"time"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
)
// ShouldPrune determines if a file with the provided modification time should be
// removed from the export log.
func (exporter *Exporter) ShouldPrune(modtime time.Time) bool {
return time.Since(modtime) > exporter.Config.MaxBackupAge
return time.Since(modtime) > component.GetStill(exporter).Config.MaxBackupAge
}
// Prune prunes all old exports
@ -44,7 +46,7 @@ func (exporter *Exporter) PruneExports(ctx context.Context, progress io.Writer)
// assemble path, and then remove the file!
path := filepath.Join(sPath, entry.Name())
fmt.Fprintf(progress, "Removing %s cause it is older than %d days\n", path, exporter.Config.MaxBackupAge)
fmt.Fprintf(progress, "Removing %s cause it is older than %d days\n", path, component.GetStill(exporter).Config.MaxBackupAge)
if err := os.Remove(path); err != nil {
return err

View file

@ -7,6 +7,7 @@ import (
"github.com/FAU-CDI/wisski-distillery/internal/config"
"github.com/FAU-CDI/wisski-distillery/internal/config/validators"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
"github.com/FAU-CDI/wisski-distillery/internal/models"
"github.com/FAU-CDI/wisski-distillery/internal/wisski"
)
@ -37,22 +38,24 @@ func (instances *Instances) Create(slug string, system models.System) (wissKI *w
wissKI.Liquid.Instance.OwnerEmail = ""
wissKI.Liquid.Instance.AutoBlindUpdateEnabled = true
config := component.GetStill(instances).Config
// sql
wissKI.Liquid.Instance.SqlDatabase = instances.Config.SQL.DataPrefix + slug
wissKI.Liquid.Instance.SqlUsername = instances.Config.SQL.UserPrefix + slug
wissKI.Liquid.Instance.SqlDatabase = config.SQL.DataPrefix + slug
wissKI.Liquid.Instance.SqlUsername = config.SQL.UserPrefix + slug
wissKI.Liquid.Instance.SqlPassword, err = instances.Config.NewPassword()
wissKI.Liquid.Instance.SqlPassword, err = config.NewPassword()
if err != nil {
return nil, err
}
// triplestore
wissKI.Liquid.Instance.GraphDBRepository = instances.Config.TS.DataPrefix + slug
wissKI.Liquid.Instance.GraphDBUsername = instances.Config.TS.UserPrefix + slug
wissKI.Liquid.Instance.GraphDBRepository = config.TS.DataPrefix + slug
wissKI.Liquid.Instance.GraphDBUsername = config.TS.UserPrefix + slug
wissKI.Liquid.Instance.GraphDBPassword, err = instances.Config.NewPassword()
wissKI.Liquid.Instance.GraphDBPassword, err = config.NewPassword()
if err != nil {
return nil, err
}
@ -61,7 +64,7 @@ func (instances *Instances) Create(slug string, system models.System) (wissKI *w
wissKI.Liquid.DrupalUsername = "admin" // TODO: Change this!
wissKI.Liquid.DrupalPassword, err = instances.Config.NewPassword()
wissKI.Liquid.DrupalPassword, err = config.NewPassword()
if err != nil {
return nil, err
}

View file

@ -28,7 +28,7 @@ type Instances struct {
}
func (instances *Instances) Path() string {
return filepath.Join(instances.Still.Config.Paths.Root, "instances")
return filepath.Join(component.GetStill(instances).Config.Paths.Root, "instances")
}
// ErrWissKINotFound is returned when a WissKI is not found

View file

@ -6,6 +6,7 @@ import (
"fmt"
"io"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
"github.com/FAU-CDI/wisski-distillery/pkg/unpack"
"github.com/tkw1536/goprogram/exit"
)
@ -22,7 +23,7 @@ var runtimeResources embed.FS
// Update installs or updates runtime components needed by this component.
func (instances *Instances) Update(ctx context.Context, progress io.Writer) error {
err := unpack.InstallDir(instances.Config.Paths.RuntimeDir(), "runtime", runtimeResources, func(dst, src string) {
err := unpack.InstallDir(component.GetStill(instances).Config.Paths.RuntimeDir(), "runtime", runtimeResources, func(dst, src string) {
fmt.Fprintf(progress, "[copy] %s\n", dst)
})
if err != nil {

View file

@ -32,7 +32,7 @@ func (api *API) Routes() component.Routes {
func (a *API) HandleRoute(ctx context.Context, path string) (http.Handler, error) {
return &api.Handler[string]{
Config: a.Config,
Config: component.GetStill(a).Config,
Auth: a.dependencies.Auth,
Methods: []string{"GET"},

View file

@ -85,15 +85,17 @@ func (resolver *Resolver) HandleRoute(ctx context.Context, route string) (http.H
Data: map[string]string{},
}
config := component.GetStill(resolver).Config
// handle the default domain name!
domainName := resolver.Config.HTTP.PrimaryDomain
domainName := config.HTTP.PrimaryDomain
if domainName != "" {
fallback.Data[fmt.Sprintf("^https?://(.*)\\.%s", regexp.QuoteMeta(domainName))] = fmt.Sprintf("https://$1.%s", domainName)
logger.Info().Str("name", domainName).Msg("registering default domain")
}
// handle the extra domains!
for _, domain := range resolver.Config.HTTP.ExtraDomains {
for _, domain := range config.HTTP.ExtraDomains {
fallback.Data[fmt.Sprintf("^https?://(.*)\\.%s", regexp.QuoteMeta(domain))] = fmt.Sprintf("https://$1.%s", domainName)
logger.Info().Str("name", domainName).Msg("registering legacy domain")
}

View file

@ -78,7 +78,7 @@ func (admin *Admin) Status(ctx context.Context, QuickInformation bool) (target s
func (admin *Admin) Fetch(flags component.FetcherFlags, target *status.Distillery) error {
target.Time = time.Now().UTC()
target.Config = admin.Config
target.Config = component.GetStill(admin).Config
return nil
}

View file

@ -56,9 +56,10 @@ func (admin *Admin) instanceSSH(ctx context.Context) http.Handler {
return ctx, nil, httpx.ErrNotFound
}
config := component.GetStill(admin).Config
ctx.Hostname = ctx.Instance.Domain()
ctx.PanelDomain = admin.Config.HTTP.PanelDomain()
ctx.Port = admin.Config.Listen.SSHPort
ctx.PanelDomain = config.HTTP.PanelDomain()
ctx.Port = config.Listen.SSHPort
keys, err := ctx.Instance.SSH().Keys(r.Context())
if err != nil {

View file

@ -99,11 +99,12 @@ func (control *Cron) Once(ctx context.Context) {
//
// The returned channel is closed once no more cron tasks are active.
func (control *Cron) Start(ctx context.Context, signal <-chan struct{}) <-chan struct{} {
zerolog.Ctx(ctx).Info().Dur("interval", control.Config.CronInterval).Msg("Scheduling Cron() tasks")
interval := component.GetStill(control).Config.CronInterval
zerolog.Ctx(ctx).Info().Dur("interval", interval).Msg("Scheduling Cron() tasks")
// run runs cron tasks with the configured timeout
run := func() {
ctx, done := context.WithTimeout(ctx, control.Config.CronInterval)
ctx, done := context.WithTimeout(ctx, interval)
defer done()
control.Once(ctx)
@ -123,7 +124,7 @@ func (control *Cron) Start(ctx context.Context, signal <-chan struct{}) <-chan s
defer timex.ReleaseTimer(t)
for {
timex.StopTimer(t)
t.Reset(control.Config.CronInterval)
t.Reset(interval)
select {
case <-t.C:

View file

@ -37,7 +37,8 @@ func (h *Handling) interceptor(parent httpx.ErrInterceptor) httpx.ErrInterceptor
pf = func(r *http.Request, err error) {}
}
parent.RenderError = h.Config.HTTP.Debug.Set && h.Config.HTTP.Debug.Value
config := component.GetStill(h).Config
parent.RenderError = config.HTTP.Debug.Set && config.HTTP.Debug.Value
parent.OnFallback = func(r *http.Request, err error) {
pf(r, err)

View file

@ -30,7 +30,7 @@ func (home *Home) Routes() component.Routes {
MatchAllDomains: true,
CSRF: false,
MenuTitle: home.Config.Home.Title,
MenuTitle: component.GetStill(home).Config.Home.Title,
MenuSticky: true,
MenuPriority: component.MenuHome,
}
@ -45,7 +45,7 @@ func (home *Home) HandleRoute(ctx context.Context, route string) (http.Handler,
dflt.Fallback = home.publicHandler(ctx)
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
slug, ok := home.Config.HTTP.NormSlugFromHost(r.Host)
slug, ok := component.GetStill(home).Config.HTTP.NormSlugFromHost(r.Host)
switch {
case !ok:
http.NotFound(w, r)

View file

@ -48,14 +48,14 @@ type publicContext struct {
const logoHTML = template.HTML(`<img src="/logo.svg" alt="WissKI Distillery Logo" class="biglogo">`)
func (home *Home) publicHandler(ctx context.Context) http.Handler {
title := home.Config.Home.Title
config := component.GetStill(home).Config.Home
tpl := publicTemplate.Prepare(
home.dependencies.Templating,
// set title and menu item
templating.Title(title),
templating.Title(config.Title),
templating.Crumbs(
component.MenuItem{Title: title, Path: "/"},
component.MenuItem{Title: config.Title, Path: "/"},
),
)
@ -73,7 +73,7 @@ func (home *Home) publicHandler(ctx context.Context) http.Handler {
// prepare about
pc.aboutContext.Logo = logoHTML
pc.aboutContext.Instances = home.dependencies.ListInstances.Infos()
pc.aboutContext.SelfRedirect = home.Config.Home.SelfRedirect.String()
pc.aboutContext.SelfRedirect = config.SelfRedirect.String()
// render the about template
@ -88,7 +88,7 @@ func (home *Home) publicHandler(ctx context.Context) http.Handler {
pc.ListEnabled = home.dependencies.ListInstances.ShouldShowList(r)
// title of the list
pc.ListTitle = home.Config.Home.List.Title
pc.ListTitle = config.List.Title
return
})

View file

@ -6,6 +6,8 @@ import (
"net/http"
"os"
"strings"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
)
func (home *Home) loadRedirect(ctx context.Context) (redirect Redirect, err error) {
@ -19,7 +21,7 @@ func (home *Home) loadRedirect(ctx context.Context) (redirect Redirect, err erro
redirect.Permanent = false
// load the overrides file
overrides, err := os.Open(home.Config.Paths.OverridesJSON)
overrides, err := os.Open(component.GetStill(home).Config.Paths.OverridesJSON)
if err != nil {
return redirect, err
}

View file

@ -41,7 +41,7 @@ type APISystem struct {
func (a *API) HandleRoute(ctx context.Context, path string) (http.Handler, error) {
return &api.Handler[[]APISystem]{
Config: a.Config,
Config: component.GetStill(a).Config,
Auth: a.dependencies.Auth,
Methods: []string{"GET"},

View file

@ -35,8 +35,9 @@ func (li *ListInstances) Infos() []status.WissKI {
// ShouldShowList determines if a list should be shown for the given request
func (li *ListInstances) ShouldShowList(r *http.Request) bool {
allowPrivate := li.Config.Home.List.Private.Value
allowPublic := li.Config.Home.List.Public.Value
config := component.GetStill(li).Config.Home.List
allowPrivate := config.Private.Value
allowPublic := config.Public.Value
if allowPrivate == allowPublic {
return allowPrivate

View file

@ -30,7 +30,7 @@ func (api *API) Routes() component.Routes {
func (a *API) HandleRoute(ctx context.Context, path string) (http.Handler, error) {
return &api.Handler[[]Item]{
Config: a.Config,
Config: component.GetStill(a).Config,
Auth: a.dependencies.Auth,
Methods: []string{"GET"},

View file

@ -53,7 +53,7 @@ func (server *Server) Server(ctx context.Context, progress io.Writer) (public ht
}()
// determine if we are on a slug from a host
slug, ok := server.Config.HTTP.NormSlugFromHost(r.Host)
slug, ok := component.GetStill(server).Config.HTTP.NormSlugFromHost(r.Host)
rctx := component.WithRouteContext(r.Context(), component.RouteContext{
DefaultDomain: slug == "" && ok,
@ -126,13 +126,15 @@ func (server *Server) Server(ctx context.Context, progress io.Writer) (public ht
// CSRF returns a CSRF handler for the given function
func (server *Server) csrf() func(http.Handler) http.Handler {
config := component.GetStill(server).Config
var opts []csrf.Option
opts = append(opts, csrf.Secure(server.Config.HTTP.HTTPSEnabled()))
opts = append(opts, csrf.Secure(config.HTTP.HTTPSEnabled()))
opts = append(opts, csrf.SameSite(csrf.SameSiteStrictMode))
opts = append(opts, csrf.Path("/"))
opts = append(opts, csrf.CookieName(CSRFCookie))
opts = append(opts, csrf.FieldName(CSRFCookieField))
return csrf.Protect(server.Config.CSRFSecret(), opts...)
return csrf.Protect(config.CSRFSecret(), opts...)
}
// WithCSP adds a Content-Security-Policy header to every response

View file

@ -11,28 +11,30 @@ import (
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
)
func (control Server) Path() string {
return filepath.Join(control.Still.Config.Paths.Root, "core", "dis")
func (server *Server) Path() string {
return filepath.Join(component.GetStill(server).Config.Paths.Root, "core", "dis")
}
//go:embed all:server
var resources embed.FS
func (server *Server) Stack() component.StackWithResources {
config := component.GetStill(server).Config
return component.MakeStack(server, component.StackWithResources{
Resources: resources,
ContextPath: "server",
EnvContext: map[string]string{
"DOCKER_NETWORK_NAME": server.Config.Docker.Network(),
"HOST_RULE": server.Config.HTTP.PanelHostRule(),
"HTTPS_ENABLED": server.Config.HTTP.HTTPSEnabledEnv(),
"DOCKER_NETWORK_NAME": config.Docker.Network(),
"HOST_RULE": config.HTTP.PanelHostRule(),
"HTTPS_ENABLED": config.HTTP.HTTPSEnabledEnv(),
"CONFIG_PATH": server.Config.ConfigPath,
"DEPLOY_ROOT": server.Config.Paths.Root,
"CONFIG_PATH": config.ConfigPath,
"DEPLOY_ROOT": config.Paths.Root,
"SELF_OVERRIDES_FILE": server.Config.Paths.OverridesJSON,
"SELF_RESOLVER_BLOCK_FILE": server.Config.Paths.ResolverBlocks,
"SELF_OVERRIDES_FILE": config.Paths.OverridesJSON,
"SELF_RESOLVER_BLOCK_FILE": config.Paths.ResolverBlocks,
"CUSTOM_ASSETS_PATH": server.dependencies.Templating.CustomAssetsPath(),
},
@ -48,6 +50,6 @@ func (server *Server) Trigger(ctx context.Context) error {
func (server *Server) Context(parent component.InstallationContext) component.InstallationContext {
return component.InstallationContext{
bootstrap.Executable: server.Config.Paths.CurrentExecutable(), // TODO: Does this make sense?
bootstrap.Executable: component.GetStill(server).Config.Paths.CurrentExecutable(), // TODO: Does this make sense?
}
}

View file

@ -8,7 +8,7 @@ import (
// CustomAssetsPath is the path custom assets are stored at
func (tpl *Templating) CustomAssetsPath() string {
return filepath.Join(tpl.Config.Paths.Root, "core", "assets")
return filepath.Join(component.GetStill(tpl).Config.Paths.Root, "core", "assets")
}
func (tpl *Templating) CustomAssetPath(name string) string {

View file

@ -21,7 +21,7 @@ var (
)
func (s *Solr) Path() string {
return filepath.Join(s.Still.Config.Paths.Root, "core", "solr")
return filepath.Join(component.GetStill(s).Config.Paths.Root, "core", "solr")
}
func (*Solr) Context(parent component.InstallationContext) component.InstallationContext {
@ -37,7 +37,7 @@ func (solr *Solr) Stack() component.StackWithResources {
ContextPath: "solr",
EnvContext: map[string]string{
"DOCKER_NETWORK_NAME": solr.Config.Docker.Network(),
"DOCKER_NETWORK_NAME": component.GetStill(solr).Config.Docker.Network(),
},
MakeDirs: []string{

View file

@ -48,7 +48,7 @@ func (sql *SQL) QueryTable(ctx context.Context, table component.Table) (*gorm.DB
// queryTable returns a gorm.DB to connect to the provided distillery database table
func (sql *SQL) queryTable(ctx context.Context, silent bool, table string) (*gorm.DB, error) {
conn, err := sql.connect(sql.Config.SQL.Database)
conn, err := sql.connect(component.GetStill(sql).Config.SQL.Database)
if err != nil {
return nil, err
}
@ -113,8 +113,9 @@ func (ssql *SQL) connect(database string) (*sql.DB, error) {
// dsn returns a dsn fof connecting to the database
func (sql *SQL) dsn(database string) string {
user := sql.Config.SQL.AdminUsername
pass := sql.Config.SQL.AdminPassword
config := component.GetStill(sql).Config.SQL
user := config.AdminUsername
pass := config.AdminPassword
network := "tcp"
server := sql.ServerURL

View file

@ -5,7 +5,7 @@ import (
"path/filepath"
"time"
"github.com/FAU-CDI/wisski-distillery/internal/config"
config_package "github.com/FAU-CDI/wisski-distillery/internal/config"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
"github.com/tkw1536/pkglib/fsx/umaskfree"
"github.com/tkw1536/pkglib/yamlx"
@ -32,7 +32,7 @@ var (
)
func (sql *SQL) Path() string {
return filepath.Join(sql.Still.Config.Paths.Root, "core", "sql")
return filepath.Join(component.GetStill(sql).Config.Paths.Root, "core", "sql")
}
func (*SQL) Context(parent component.InstallationContext) component.InstallationContext {
@ -43,19 +43,20 @@ func (*SQL) Context(parent component.InstallationContext) component.Installation
var resources embed.FS
func (sql *SQL) Stack() component.StackWithResources {
config := component.GetStill(sql).Config
return component.MakeStack(sql, component.StackWithResources{
Resources: resources,
ContextPath: "sql",
EnvContext: map[string]string{
"DOCKER_NETWORK_NAME": sql.Config.Docker.Network(),
"HTTPS_ENABLED": sql.Config.HTTP.HTTPSEnabledEnv(),
"HOST_RULE": sql.Config.HTTP.HostRule(config.PHPMyAdminDomain.Domain()),
"DOCKER_NETWORK_NAME": config.Docker.Network(),
"HTTPS_ENABLED": config.HTTP.HTTPSEnabledEnv(),
"HOST_RULE": config.HTTP.HostRule(config_package.PHPMyAdminDomain.Domain()),
},
ComposerYML: func(root *yaml.Node) (*yaml.Node, error) {
// phpmyadmin is exposed => everything is fine
if sql.Config.HTTP.PhpMyAdmin.Set && sql.Config.HTTP.PhpMyAdmin.Value {
if config.HTTP.PhpMyAdmin.Set && config.HTTP.PhpMyAdmin.Value {
return root, nil
}

View file

@ -8,6 +8,7 @@ import (
"reflect"
"time"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
"github.com/FAU-CDI/wisski-distillery/pkg/logging"
"github.com/tkw1536/goprogram/exit"
"github.com/tkw1536/pkglib/sqlx"
@ -68,6 +69,7 @@ var errSQLUnableToMigrate = exit.Error{
// Update initializes or updates the SQL database.
func (sql *SQL) Update(ctx context.Context, progress io.Writer) error {
config := component.GetStill(sql).Config.SQL
// unsafely create the admin user!
{
@ -76,8 +78,8 @@ func (sql *SQL) Update(ctx context.Context, progress io.Writer) error {
}
logging.LogMessage(progress, "Creating administrative user")
{
username := sql.Config.SQL.AdminUsername
password := sql.Config.SQL.AdminPassword
username := config.AdminUsername
password := config.AdminPassword
if err := sql.CreateSuperuser(ctx, username, password, true); err != nil {
return errSQLUnableToCreateUser
}
@ -87,10 +89,10 @@ func (sql *SQL) Update(ctx context.Context, progress io.Writer) error {
// create the admin user
logging.LogMessage(progress, "Creating sql database")
{
if !sqlx.IsSafeDatabaseLiteral(sql.Config.SQL.Database) {
if !sqlx.IsSafeDatabaseLiteral(config.Database) {
return errSQLUnsafeDatabaseName
}
createDBSQL := fmt.Sprintf("CREATE DATABASE IF NOT EXISTS `%s`;", sql.Config.SQL.Database)
createDBSQL := fmt.Sprintf("CREATE DATABASE IF NOT EXISTS `%s`;", config.Database)
if err := sql.Exec(createDBSQL); err != nil {
return err
}

View file

@ -26,7 +26,7 @@ func (ssh2 *SSH2) HandleRoute(ctx context.Context, path string) (http.Handler, e
}
// find the host
slug, ok := ssh2.Config.HTTP.SlugFromHost(r.Host)
slug, ok := component.GetStill(ssh2).Config.HTTP.SlugFromHost(r.Host)
if slug == "" || !ok {
httpx.TextInterceptor.Intercept(w, r, httpx.ErrNotFound)
return

View file

@ -58,10 +58,11 @@ func (i Intercept) Intercept(req component.HostPort) (intercepted bool, ok bool,
}
func (ssh2 *SSH2) Intercepts() []Intercept {
upstream := component.GetStill(ssh2).Upstream
return ssh2.interceptsC.Get(func() []Intercept {
return []Intercept{
{Description: "Triplestore", Match: component.HostPort{Host: "triplestore", Port: 7200}, Dest: ssh2.Upstream.Triplestore},
{Description: "SQL", Match: component.HostPort{Host: "sql", Port: 3306}, Dest: ssh2.Upstream.SQL},
{Description: "Triplestore", Match: component.HostPort{Host: "triplestore", Port: 7200}, Dest: upstream.Triplestore},
{Description: "SQL", Match: component.HostPort{Host: "sql", Port: 3306}, Dest: upstream.SQL},
{Description: "PHPMyAdmin", Match: component.HostPort{Host: "phpmyadmin", Port: 80}, Dest: component.HostPort{Host: "phpmyadmin", Port: 80}},
}
})
@ -77,13 +78,15 @@ func (ssh2 *SSH2) getForwardDest(req component.HostPort, ctx ssh.Context) (ok bo
return ok, dest, rejectReason
}
config := component.GetStill(ssh2).Config
// then check the instances
slug, ok := ssh2.Config.HTTP.SlugFromHost(req.Host)
slug, ok := config.HTTP.SlugFromHost(req.Host)
if !ok || req.Port != 22 || !hasPermission(ctx, slug) {
return false, dest, "permission denied"
}
return true, component.HostPort{Host: slug + "." + ssh2.Config.HTTP.PrimaryDomain + ".wisski", Port: 22}, ""
return true, component.HostPort{Host: slug + "." + config.HTTP.PrimaryDomain + ".wisski", Port: 22}, ""
}
// handleDirectTCP handles a direct tcp connection for the server

View file

@ -6,6 +6,7 @@ import (
"strconv"
"strings"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
"github.com/gliderlabs/ssh"
)
@ -38,17 +39,18 @@ Press CTRL-C to close this connection.
`
func (ssh2 *SSH2) handleConnection(session ssh.Session) {
config := component.GetStill(ssh2).Config
slug, _ := getAnyPermission(session.Context())
banner := welcomeMessage
for _, oldnew := range [][2]string{
{"${SLUG}", slug},
{"${HOSTNAME}", slug + "." + ssh2.Config.HTTP.PrimaryDomain},
{"${HOSTNAME}", slug + "." + config.HTTP.PrimaryDomain},
{"${DOMAIN}", ssh2.Config.HTTP.PanelDomain()},
{"${PORT}", strconv.FormatUint(uint64(ssh2.Config.Listen.SSHPort), 10)},
{"${DOMAIN}", config.HTTP.PanelDomain()},
{"${PORT}", strconv.FormatUint(uint64(config.Listen.SSHPort), 10)},
{"${HELP_URL}", ssh2.Config.HTTP.JoinPath("user", "ssh").String()},
{"${HELP_URL}", config.HTTP.JoinPath("user", "ssh").String()},
} {
banner = strings.ReplaceAll(banner, oldnew[0], oldnew[1])
}

View file

@ -9,25 +9,26 @@ import (
)
func (ssh *SSH2) Path() string {
return filepath.Join(ssh.Still.Config.Paths.Root, "core", "ssh2")
return filepath.Join(component.GetStill(ssh).Config.Paths.Root, "core", "ssh2")
}
//go:embed all:ssh2
var resources embed.FS
func (ssh *SSH2) Stack() component.StackWithResources {
config := component.GetStill(ssh).Config
return component.MakeStack(ssh, component.StackWithResources{
Resources: resources,
ContextPath: "ssh2",
EnvContext: map[string]string{
"DOCKER_NETWORK_NAME": ssh.Config.Docker.Network(),
"DOCKER_NETWORK_NAME": config.Docker.Network(),
"CONFIG_PATH": ssh.Config.ConfigPath,
"DEPLOY_ROOT": ssh.Config.Paths.Root,
"CONFIG_PATH": config.ConfigPath,
"DEPLOY_ROOT": config.Paths.Root,
"SELF_OVERRIDES_FILE": ssh.Config.Paths.OverridesJSON,
"SELF_RESOLVER_BLOCK_FILE": ssh.Config.Paths.ResolverBlocks,
"SELF_OVERRIDES_FILE": config.Paths.OverridesJSON,
"SELF_RESOLVER_BLOCK_FILE": config.Paths.ResolverBlocks,
},
CopyContextFiles: []string{bootstrap.Executable},
@ -36,6 +37,6 @@ func (ssh *SSH2) Stack() component.StackWithResources {
func (ssh *SSH2) Context(parent component.InstallationContext) component.InstallationContext {
return component.InstallationContext{
bootstrap.Executable: ssh.Config.Paths.CurrentExecutable(), // TODO: Does this make sense?
bootstrap.Executable: component.GetStill(ssh).Config.Paths.CurrentExecutable(), // TODO: Does this make sense?
}
}

View file

@ -9,6 +9,7 @@ import (
"net/http"
"time"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
"github.com/pkg/errors"
"github.com/rs/zerolog"
"github.com/tkw1536/pkglib/timex"
@ -58,12 +59,12 @@ func (rh *RequestHeaders) With(headers RequestHeaders) *RequestHeaders {
}
// DoRest performs a (raw) http request to the without a body.
func (ts Triplestore) DoRest(ctx context.Context, timeout time.Duration, method, url string, headers *RequestHeaders) (*http.Response, error) {
func (ts *Triplestore) DoRest(ctx context.Context, timeout time.Duration, method, url string, headers *RequestHeaders) (*http.Response, error) {
return ts.DoRestWithReader(ctx, timeout, method, url, headers, nil)
}
// DoRestWithForm performs a http request where the body are all bytes read from fieldvalue.
func (ts Triplestore) DoRestWithForm(ctx context.Context, timeout time.Duration, method, url string, headers *RequestHeaders, fieldname string, fieldvalue io.Reader) (*http.Response, error) {
func (ts *Triplestore) DoRestWithForm(ctx context.Context, timeout time.Duration, method, url string, headers *RequestHeaders, fieldname string, fieldvalue io.Reader) (*http.Response, error) {
var buffer bytes.Buffer
// write the file to it
@ -83,7 +84,7 @@ func (ts Triplestore) DoRestWithForm(ctx context.Context, timeout time.Duration,
// DoRestWithReader performs a http request where the body is copied from the given io.Reader.
// The caller must ensure the reader is closed.
func (ts Triplestore) DoRestWithMarshal(ctx context.Context, timeout time.Duration, method, url string, headers *RequestHeaders, body any) (*http.Response, error) {
func (ts *Triplestore) DoRestWithMarshal(ctx context.Context, timeout time.Duration, method, url string, headers *RequestHeaders, body any) (*http.Response, error) {
// encode into a buffer
var buffer bytes.Buffer
if err := json.NewEncoder(&buffer).Encode(body); err != nil {
@ -95,7 +96,7 @@ func (ts Triplestore) DoRestWithMarshal(ctx context.Context, timeout time.Durati
// DoRestWithReader performs a http request where the body is copied from the given io.Reader.
// The caller must ensure the reader is closed.
func (ts Triplestore) DoRestWithReader(ctx context.Context, timeout time.Duration, method string, url string, headers *RequestHeaders, body io.Reader) (*http.Response, error) {
func (ts *Triplestore) DoRestWithReader(ctx context.Context, timeout time.Duration, method string, url string, headers *RequestHeaders, body io.Reader) (*http.Response, error) {
// create the request object
client := &http.Client{
Timeout: timeout,
@ -104,12 +105,14 @@ func (ts Triplestore) DoRestWithReader(ctx context.Context, timeout time.Duratio
},
}
config := component.GetStill(ts).Config.TS
// create the request and authentication
req, err := http.NewRequestWithContext(ctx, method, ts.BaseURL+url, body)
if err != nil {
return nil, err
}
req.SetBasicAuth(ts.Config.TS.AdminUsername, ts.Config.TS.AdminPassword)
req.SetBasicAuth(config.AdminUsername, config.AdminPassword)
// add extra headers
if headers != nil && headers.Accept != "" {

View file

@ -5,7 +5,7 @@ import (
"path/filepath"
"time"
"github.com/FAU-CDI/wisski-distillery/internal/config"
config_package "github.com/FAU-CDI/wisski-distillery/internal/config"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
"github.com/tkw1536/pkglib/yamlx"
"gopkg.in/yaml.v3"
@ -27,7 +27,7 @@ var (
)
func (ts *Triplestore) Path() string {
return filepath.Join(ts.Still.Config.Paths.Root, "core", "triplestore")
return filepath.Join(component.GetStill(ts).Config.Paths.Root, "core", "triplestore")
}
func (Triplestore) Context(parent component.InstallationContext) component.InstallationContext {
@ -38,6 +38,7 @@ func (Triplestore) Context(parent component.InstallationContext) component.Insta
var resources embed.FS
func (ts *Triplestore) Stack() component.StackWithResources {
config := component.GetStill(ts).Config
return component.MakeStack(ts, component.StackWithResources{
Resources: resources,
ContextPath: "triplestore",
@ -45,14 +46,14 @@ func (ts *Triplestore) Stack() component.StackWithResources {
CopyContextFiles: []string{"graphdb.zip"}, // TODO: Move into constant?
EnvContext: map[string]string{
"DOCKER_NETWORK_NAME": ts.Config.Docker.Network(),
"HOST_RULE": ts.Config.HTTP.HostRule(config.TriplestoreDomain.Domain()),
"HTTPS_ENABLED": ts.Config.HTTP.HTTPSEnabledEnv(),
"DOCKER_NETWORK_NAME": config.Docker.Network(),
"HOST_RULE": config.HTTP.HostRule(config_package.TriplestoreDomain.Domain()),
"HTTPS_ENABLED": config.HTTP.HTTPSEnabledEnv(),
},
ComposerYML: func(root *yaml.Node) (*yaml.Node, error) {
// ts is exposed => everything is fine
if ts.Config.HTTP.TS.Set && ts.Config.HTTP.TS.Value {
if config.HTTP.TS.Set && config.HTTP.TS.Value {
return root, nil
}

View file

@ -6,13 +6,14 @@ import (
"io"
"net/http"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
"github.com/FAU-CDI/wisski-distillery/pkg/logging"
"github.com/pkg/errors"
)
var errTriplestoreFailedSecurity = errors.New("failed to enable triplestore security: request did not succeed with HTTP 200 OK")
func (ts Triplestore) Update(ctx context.Context, progress io.Writer) error {
func (ts *Triplestore) Update(ctx context.Context, progress io.Writer) error {
logging.LogMessage(progress, "Waiting for Triplestore")
if err := ts.Wait(ctx); err != nil {
return err
@ -20,8 +21,10 @@ func (ts Triplestore) Update(ctx context.Context, progress io.Writer) error {
logging.LogMessage(progress, "Resetting admin user password")
{
res, err := ts.DoRestWithMarshal(ctx, tsTrivialTimeout, http.MethodPut, "/rest/security/users/"+ts.Config.TS.AdminUsername, nil, TriplestoreUserPayload{
Password: ts.Config.TS.AdminPassword,
config := component.GetStill(ts).Config.TS
res, err := ts.DoRestWithMarshal(ctx, tsTrivialTimeout, http.MethodPut, "/rest/security/users/"+config.AdminUsername, nil, TriplestoreUserPayload{
Password: config.AdminPassword,
AppSettings: TriplestoreUserAppSettings{
DefaultInference: true,
DefaultVisGraphSchema: true,

View file

@ -21,7 +21,7 @@ var (
)
func (web *Web) Path() string {
return filepath.Join(web.Still.Config.Paths.Root, "core", "web")
return filepath.Join(component.GetStill(web).Config.Paths.Root, "core", "web")
}
func (*Web) Context(parent component.InstallationContext) component.InstallationContext {
@ -37,12 +37,13 @@ var dockerComposeHTTPS []byte
func (web *Web) Stack() component.StackWithResources {
var stack component.StackWithResources
config := component.GetStill(web).Config
stack.EnvContext = map[string]string{
"DOCKER_NETWORK_NAME": web.Config.Docker.Network(),
"CERT_EMAIL": web.Config.HTTP.CertbotEmail,
"DOCKER_NETWORK_NAME": config.Docker.Network(),
"CERT_EMAIL": config.HTTP.CertbotEmail,
}
if web.Config.HTTP.HTTPSEnabled() {
if config.HTTP.HTTPSEnabled() {
stack.ComposerYML = readYaml(dockerComposeHTTPS)
stack.TouchFilesPerm = 0600
stack.TouchFiles = []string{"acme.json"}