diff --git a/cmd/build/build-image.go b/cmd/build/build-image.go index b29f3d0..ceef5d7 100644 --- a/cmd/build/build-image.go +++ b/cmd/build/build-image.go @@ -34,27 +34,27 @@ func newBuildImage(b *build) *buildImage { return &buildImage{ build: b, - defaultGenericConfigFile: "contrib/configurations/dummy-for-oci-images.yaml", - defaultExtendedConfigFile: "contrib/configurations/sshd-dropin-replacement.yaml", + dummyConfiguration: "cmd/build/contrib/dummy-for-oci-images.yaml", + defaultConfiguration: "contrib/configurations/sshd-dropin-replacement.yaml", } } type buildImage struct { *build - defaultGenericConfigFile string - defaultExtendedConfigFile string + dummyConfiguration string + defaultConfiguration string } func (this *buildImage) attach(cmd *kingpin.CmdClause) { - cmd.Flag("defaultConfigFile", ""). - Default(this.defaultGenericConfigFile). + cmd.Flag("dummyConfiguration", ""). + Default(this.dummyConfiguration). PlaceHolder(""). - StringVar(&this.defaultGenericConfigFile) - cmd.Flag("defaultExtendedConfigFile", ""). - Default(this.defaultExtendedConfigFile). + StringVar(&this.dummyConfiguration) + cmd.Flag("defaultConfiguration", ""). + Default(this.defaultConfiguration). PlaceHolder(""). - StringVar(&this.defaultExtendedConfigFile) + StringVar(&this.defaultConfiguration) } func (this *buildImage) create(ctx context.Context, binary *buildArtifact) (_ buildArtifacts, rErr error) { @@ -162,39 +162,38 @@ func (this *buildImage) createPart(ctx context.Context, binary *buildArtifact) ( img = mutate.Annotations(img, annotations).(gcv1.Image) - artifacts := common.Seq2ErrOf[imageArtifactLayerItem](imageArtifactLayerItem{ - sourceFile: this.defaultExtendedConfigFile, + artifacts := []imageArtifactLayerItem{{ + sourceFile: this.defaultConfiguration, targetFile: binary.platform.os.bifroestConfigFilePath(), mode: 0644, - }) - if !a.os.isUnix() { - artifacts = common.Seq2ErrOf[imageArtifactLayerItem](imageArtifactLayerItem{ - sourceFile: this.defaultGenericConfigFile, - targetFile: binary.platform.os.bifroestConfigFilePath(), - mode: 0644, - }) - } else if strings.EqualFold(from, "scratch") { - artifacts = common.Seq2ErrOf[imageArtifactLayerItem](imageArtifactLayerItem{ - sourceFile: this.defaultGenericConfigFile, + }} + + if !a.os.isUnix() || strings.EqualFold(from, "scratch") { + artifacts = []imageArtifactLayerItem{{ + sourceFile: this.dummyConfiguration, targetFile: binary.platform.os.bifroestConfigFilePath(), mode: 0644, - }, imageArtifactLayerItem{ - sourceFile: "contrib/rootfs/etc/passwd", + }} + } + + if a.os.isUnix() && strings.EqualFold(from, "scratch") { + artifacts = append(artifacts, imageArtifactLayerItem{ + sourceFile: "cmd/build/contrib/passwd", targetFile: "/etc/passwd", mode: 0644, }, imageArtifactLayerItem{ - sourceFile: "contrib/rootfs/etc/group", + sourceFile: "cmd/build/contrib/group", targetFile: "/etc/group", mode: 0644, }, imageArtifactLayerItem{ - sourceFile: "contrib/rootfs/etc/shadow", + sourceFile: "cmd/build/contrib/shadow", targetFile: "/etc/shadow", mode: 0600, }) } binaryLayer, err := binary.toLayer(common.JoinSeq2[imageArtifactLayerItem, error]( - artifacts, + common.Seq2ErrOf[imageArtifactLayerItem](artifacts...), common.Seq2ErrOf[imageArtifactLayerItem](artifactDepItems...), )) if err != nil { diff --git a/cmd/build/contrib/dummy-for-oci-images.yaml b/cmd/build/contrib/dummy-for-oci-images.yaml new file mode 100644 index 0000000..37ca1db --- /dev/null +++ b/cmd/build/contrib/dummy-for-oci-images.yaml @@ -0,0 +1,31 @@ +## This configuration should only be used as dummy configuration within OCI/Docker images. +## It will create (if not exists) +## for the regular sshd. + +startMessage: > + Welcome to Engity's Bifröst! + This is instance runs with a demo configuration which should and is + NOT intended for production use. Therefore login to this instance + is not possible until it will be configured. + See https://bifroest.engity.org/latest/setup/ for more details. + +ssh: + addresses: [ ":22" ] + banner: |+ + Transcend with Engity's Bifröst + =============================== + + This is instance runs with a demo configuration which should + and is NOT intended for production use. Therefore login to + this instance is not possible until it will be configured. + + See https://bifroest.engity.org/latest/setup/ for more details. + +flows: + - name: default + authorization: + type: htpasswd + environment: + type: local + # This property will not be evaluated in Windows. + name: "demo" diff --git a/cmd/build/contrib/group b/cmd/build/contrib/group new file mode 100644 index 0000000..1dbf901 --- /dev/null +++ b/cmd/build/contrib/group @@ -0,0 +1 @@ +root:x:0: diff --git a/cmd/build/contrib/passwd b/cmd/build/contrib/passwd new file mode 100644 index 0000000..6d9b028 --- /dev/null +++ b/cmd/build/contrib/passwd @@ -0,0 +1 @@ +root:x:0:0:root:/:/usr/bin/bifroest diff --git a/cmd/build/contrib/shadow b/cmd/build/contrib/shadow new file mode 100644 index 0000000..cafaee5 --- /dev/null +++ b/cmd/build/contrib/shadow @@ -0,0 +1 @@ +root:*:19683:0:99999:7::: diff --git a/contrib/configurations/dummy-for-oci-images.yaml b/contrib/configurations/dummy-for-oci-images.yaml deleted file mode 100644 index cb0ede5..0000000 --- a/contrib/configurations/dummy-for-oci-images.yaml +++ /dev/null @@ -1,34 +0,0 @@ -## This configuration should only be used as dummy configuration within OCI/Docker images. -## It will create (if not exists) -## for the regular sshd. - -ssh: - addresses: [ ":22" ] - banner: |+ - Transcend with Engity Bifröst - ============================= - - This is instance runs with a demo configuration which should - NEVER be used in production. - - To login to this instance see the service logs, like: - docker logs bifroest - -session: - type: fs - -flows: - - name: local - authorization: - type: htpasswd - - # DO NOT USE THIS SETTING IN PRODUCTION! - # This will create at the default location of the property "file" a file if it does not already exist - # with dummy data inside. This really only make sense to create dummy data for demo purposes. - generateWithUserIfAbsentAndWarn: true - - environment: - type: local - - # This property will not be evaluated in Windows. - name: "root" diff --git a/contrib/rootfs/etc/group b/contrib/rootfs/etc/group deleted file mode 100644 index e69de29..0000000 diff --git a/contrib/rootfs/etc/passwd b/contrib/rootfs/etc/passwd deleted file mode 100644 index e69de29..0000000 diff --git a/contrib/rootfs/etc/shadow b/contrib/rootfs/etc/shadow deleted file mode 100644 index e69de29..0000000 diff --git a/docs/reference/connection/ssh.md b/docs/reference/connection/ssh.md index a6fa195..ee28219 100644 --- a/docs/reference/connection/ssh.md +++ b/docs/reference/connection/ssh.md @@ -25,7 +25,7 @@ How many different authentication methods a client can use before the connection <> The maximum amount of parallel connections on this service. Every additional connection beyond will be rejected. -<> +<> Banner which will be shown when the client connects to the server even before the first validation of authorizations or similar happens. ## Examples diff --git a/pkg/configuration/authorization-htpasswd.go b/pkg/configuration/authorization-htpasswd.go index a5aeec4..29bbb03 100644 --- a/pkg/configuration/authorization-htpasswd.go +++ b/pkg/configuration/authorization-htpasswd.go @@ -1,19 +1,9 @@ package configuration import ( - "crypto/rand" - "fmt" - "os" - "path/filepath" - - log "github.com/echocat/slf4g" - "github.com/mr-tron/base58" - "golang.org/x/crypto/bcrypt" "gopkg.in/yaml.v3" - "github.com/engity-com/bifroest/pkg/common" "github.com/engity-com/bifroest/pkg/crypto" - "github.com/engity-com/bifroest/pkg/errors" "github.com/engity-com/bifroest/pkg/sys" ) @@ -26,9 +16,8 @@ var ( ) type AuthorizationHtpasswd struct { - File crypto.HtpasswdFile `yaml:"file,omitempty"` - Entries crypto.Htpasswd `yaml:"entries,omitempty"` - GenerateWithUserIfAbsentAndWarn bool `yaml:"generateWithUserIfAbsentAndWarn"` + File crypto.HtpasswdFile `yaml:"file,omitempty"` + Entries crypto.Htpasswd `yaml:"entries,omitempty"` } func (this *AuthorizationHtpasswd) SetDefaults() error { @@ -47,7 +36,6 @@ func (this *AuthorizationHtpasswd) SetDefaults() error { }) }, noopSetDefault[AuthorizationHtpasswd]("entries"), - noopSetDefault[AuthorizationHtpasswd]("generateWithUserIfAbsentAndWarn"), ) } @@ -55,63 +43,16 @@ func (this *AuthorizationHtpasswd) Trim() error { return trim(this, noopTrim[AuthorizationHtpasswd]("file"), noopTrim[AuthorizationHtpasswd]("entries"), - noopTrim[AuthorizationHtpasswd]("generateWithUserIfAbsentAndWarn"), ) } func (this *AuthorizationHtpasswd) Validate() (rErr error) { - if this.GenerateWithUserIfAbsentAndWarn && this.File.IsZero() && this.Entries.IsZero() { - fn, err := this.createDummyFileAndWarn() - if err != nil { - return err - } - if err := this.File.Set(fn); err != nil { - return err - } - } - return validate(this, func(v *AuthorizationHtpasswd) (string, validator) { return "file", &v.File }, func(v *AuthorizationHtpasswd) (string, validator) { return "entries", &v.Entries }, - noopValidate[AuthorizationHtpasswd]("generateWithUserIfAbsentAndWarn"), ) } -func (this *AuthorizationHtpasswd) createDummyFileAndWarn() (fn string, rErr error) { - buf := make([]byte, 12) - if n, err := rand.Read(buf); err != nil { - return "", errors.System.Newf("cannot create demo password: %w", err) - } else if n != len(buf) { - return "", errors.System.Newf("cannot create demo password: not enough entries in random source") - } - pass := base58.Encode(buf) - hash, err := bcrypt.GenerateFromPassword([]byte(pass), bcrypt.DefaultCost) - if err != nil { - return "", errors.System.Newf("cannot create demo password: %w", err) - } - - _ = os.MkdirAll(filepath.Dir(DefaultAuthorizationHtpasswdFile), 0700) - f, err := os.OpenFile(DefaultAuthorizationHtpasswdFile, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0600) - if os.IsExist(err) { - return DefaultAuthorizationHtpasswdFile, nil - } - if err != nil { - return "", errors.System.Newf("cannot create demo password in %q: %w", DefaultAuthorizationHtpasswdFile, err) - } - defer common.KeepCloseError(&rErr, f) - - if _, err = fmt.Fprintf(f, "demo:%s\n", string(hash)); err != nil { - return "", errors.System.Newf("cannot create demo password in %q: %w", DefaultAuthorizationHtpasswdFile, err) - } - - log.Warn("NOT CONFIGURED FOR PRODUCTION USE!!") - log.With("user", "demo"). - With("password", pass). - Info("as this instance runs in dummy mode; a user for demonstration purposes was created - use this credentials to login to Bifröst - this message will never be shown again!") - - return DefaultAuthorizationHtpasswdFile, nil -} - func (this *AuthorizationHtpasswd) UnmarshalYAML(node *yaml.Node) error { return unmarshalYAML(this, node, func(target *AuthorizationHtpasswd, node *yaml.Node) error { type raw AuthorizationHtpasswd @@ -135,8 +76,7 @@ func (this AuthorizationHtpasswd) IsEqualTo(other any) bool { func (this AuthorizationHtpasswd) isEqualTo(other *AuthorizationHtpasswd) bool { return isEqual(&this.File, &other.File) && - isEqual(&this.Entries, &other.Entries) && - this.GenerateWithUserIfAbsentAndWarn == other.GenerateWithUserIfAbsentAndWarn + isEqual(&this.Entries, &other.Entries) } func (this AuthorizationHtpasswd) Types() []string { diff --git a/pkg/configuration/configuration.go b/pkg/configuration/configuration.go index a2f3ce1..b114432 100644 --- a/pkg/configuration/configuration.go +++ b/pkg/configuration/configuration.go @@ -11,6 +11,10 @@ import ( "github.com/engity-com/bifroest/pkg/sys" ) +var ( + DefaultStartMessage = "" +) + type Configuration struct { Ssh Ssh `yaml:"ssh"` @@ -23,6 +27,8 @@ type Configuration struct { Flows Flows `yaml:"flows"` HouseKeeping HouseKeeping `yaml:"housekeeping"` + + StartMessage string `yaml:"startMessage,omitempty"` } func (this *Configuration) SetDefaults() error { @@ -31,6 +37,7 @@ func (this *Configuration) SetDefaults() error { func(v *Configuration) (string, defaulter) { return "session", &v.Session }, func(v *Configuration) (string, defaulter) { return "flows", &v.Flows }, func(v *Configuration) (string, defaulter) { return "houseKeeping", &v.HouseKeeping }, + fixedDefault("startMessage", func(v *Configuration) *string { return &v.StartMessage }, DefaultStartMessage), ) } @@ -40,6 +47,7 @@ func (this *Configuration) Trim() error { func(v *Configuration) (string, trimmer) { return "session", &v.Session }, func(v *Configuration) (string, trimmer) { return "flows", &v.Flows }, func(v *Configuration) (string, trimmer) { return "houseKeeping", &v.HouseKeeping }, + noopTrim[Configuration]("startMessage"), ) } @@ -50,6 +58,7 @@ func (this *Configuration) Validate() error { func(v *Configuration) (string, validator) { return "flows", &v.Flows }, notEmptySliceValidate("flows", func(v *Configuration) *[]Flow { return (*[]Flow)(&v.Flows) }), func(v *Configuration) (string, validator) { return "houseKeeping", &v.HouseKeeping }, + noopValidate[Configuration]("startMessage"), ) } @@ -111,5 +120,6 @@ func (this Configuration) isEqualTo(other *Configuration) bool { return isEqual(&this.Ssh, &other.Ssh) && isEqual(&this.Session, &other.Session) && isEqual(&this.Flows, &other.Flows) && - isEqual(&this.HouseKeeping, &other.HouseKeeping) + isEqual(&this.HouseKeeping, &other.HouseKeeping) && + this.StartMessage == other.StartMessage } diff --git a/pkg/service/service.go b/pkg/service/service.go index 618c06b..5a4eef1 100644 --- a/pkg/service/service.go +++ b/pkg/service/service.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "net" + "strings" "sync" "sync/atomic" "syscall" @@ -55,6 +56,15 @@ func (this *Service) Run(ctx context.Context) (rErr error) { if ctx == nil { ctx = context.Background() } + + if msg := this.Configuration.StartMessage; msg != "" { + for _, line := range strings.Split(msg, "\n") { + if line = strings.TrimSpace(line); line != "" { + log.Warn(line) + } + } + } + svc, err := this.prepare() if err != nil { return err