diff --git a/cmd/bifroest/main_linux.go b/cmd/bifroest/main_unix.go similarity index 84% rename from cmd/bifroest/main_linux.go rename to cmd/bifroest/main_unix.go index 14a6019..e7e4aaa 100644 --- a/cmd/bifroest/main_linux.go +++ b/cmd/bifroest/main_unix.go @@ -1,4 +1,4 @@ -//go:build linux +//go:build unix package main diff --git a/pkg/authorization/local-authorizer.go b/pkg/authorization/local-authorizer.go index 1ca0cd1..79cb9d8 100644 --- a/pkg/authorization/local-authorizer.go +++ b/pkg/authorization/local-authorizer.go @@ -1,4 +1,4 @@ -//go:build linux +//go:build unix package authorization diff --git a/pkg/authorization/local.go b/pkg/authorization/local.go index 41b837b..de957b6 100644 --- a/pkg/authorization/local.go +++ b/pkg/authorization/local.go @@ -1,4 +1,4 @@ -//go:build linux +//go:build unix package authorization diff --git a/pkg/configuration/authorization-local.go b/pkg/configuration/authorization-local.go index 5a11c5a..6b6b23d 100644 --- a/pkg/configuration/authorization-local.go +++ b/pkg/configuration/authorization-local.go @@ -1,4 +1,4 @@ -//go:build linux +//go:build unix package configuration diff --git a/pkg/configuration/configuration_linux_test.go b/pkg/configuration/configuration_unix_test.go similarity index 99% rename from pkg/configuration/configuration_linux_test.go rename to pkg/configuration/configuration_unix_test.go index c505342..52d452a 100644 --- a/pkg/configuration/configuration_linux_test.go +++ b/pkg/configuration/configuration_unix_test.go @@ -1,13 +1,15 @@ -//go:build linux +//go:build unix package configuration import ( + "testing" + "github.com/echocat/slf4g/sdk/testlog" + "github.com/engity-com/bifroest/pkg/common" "github.com/engity-com/bifroest/pkg/crypto" "github.com/engity-com/bifroest/pkg/template" - "testing" ) func TestConfiguration_UnmarshalYAML(t *testing.T) { @@ -33,7 +35,7 @@ func TestConfiguration_UnmarshalYAML(t *testing.T) { name: "required-set", yaml: `flows: - name: foo - authorization: + authorization: type: oidcDeviceAuth issuer: https://foo-bar clientId: anId diff --git a/pkg/configuration/environment-local-dispose_linux.go b/pkg/configuration/environment-local-dispose.go similarity index 99% rename from pkg/configuration/environment-local-dispose_linux.go rename to pkg/configuration/environment-local-dispose.go index dc720fb..bb5577b 100644 --- a/pkg/configuration/environment-local-dispose_linux.go +++ b/pkg/configuration/environment-local-dispose.go @@ -1,10 +1,11 @@ -//go:build linux +//go:build unix package configuration import ( - "github.com/engity-com/bifroest/pkg/template" "gopkg.in/yaml.v3" + + "github.com/engity-com/bifroest/pkg/template" ) var ( diff --git a/pkg/configuration/environment-local-dispose_windows.go b/pkg/configuration/environment-local-dispose_windows.go deleted file mode 100644 index df50603..0000000 --- a/pkg/configuration/environment-local-dispose_windows.go +++ /dev/null @@ -1,46 +0,0 @@ -//go:build windows - -package configuration - -import ( - "gopkg.in/yaml.v3" -) - -type EnvironmentLocalDispose struct{} - -func (this *EnvironmentLocalDispose) SetDefaults() error { - return setDefaults(this) -} - -func (this *EnvironmentLocalDispose) Trim() error { - return trim(this) -} - -func (this *EnvironmentLocalDispose) Validate() error { - return validate(this) -} - -func (this *EnvironmentLocalDispose) UnmarshalYAML(node *yaml.Node) error { - return unmarshalYAML(this, node, func(target *EnvironmentLocalDispose, node *yaml.Node) error { - type raw EnvironmentLocalDispose - return node.Decode((*raw)(target)) - }) -} - -func (this EnvironmentLocalDispose) IsEqualTo(other any) bool { - if other == nil { - return false - } - switch v := other.(type) { - case EnvironmentLocalDispose: - return this.isEqualTo(&v) - case *EnvironmentLocalDispose: - return this.isEqualTo(v) - default: - return false - } -} - -func (this EnvironmentLocalDispose) isEqualTo(_ *EnvironmentLocalDispose) bool { - return true -} diff --git a/pkg/configuration/environment-local.go b/pkg/configuration/environment-local.go index 9f60a7c..4aed377 100644 --- a/pkg/configuration/environment-local.go +++ b/pkg/configuration/environment-local.go @@ -1,3 +1,5 @@ +//go:build unix + package configuration import ( diff --git a/pkg/configuration/group-requirement-template_linux.go b/pkg/configuration/group-requirement-template.go similarity index 99% rename from pkg/configuration/group-requirement-template_linux.go rename to pkg/configuration/group-requirement-template.go index 604288b..8de77cd 100644 --- a/pkg/configuration/group-requirement-template_linux.go +++ b/pkg/configuration/group-requirement-template.go @@ -1,13 +1,15 @@ -//go:build linux +//go:build unix package configuration import ( "fmt" + + "gopkg.in/yaml.v3" + "github.com/engity-com/bifroest/pkg/common" "github.com/engity-com/bifroest/pkg/template" "github.com/engity-com/bifroest/pkg/user" - "gopkg.in/yaml.v3" ) var ( diff --git a/pkg/configuration/keys_linux.go b/pkg/configuration/keys_unix.go similarity index 83% rename from pkg/configuration/keys_linux.go rename to pkg/configuration/keys_unix.go index 51fb60b..cdfbc01 100644 --- a/pkg/configuration/keys_linux.go +++ b/pkg/configuration/keys_unix.go @@ -1,4 +1,4 @@ -//go:build linux +//go:build unix package configuration diff --git a/pkg/configuration/session-fs_linux.go b/pkg/configuration/session-fs_unix.go similarity index 84% rename from pkg/configuration/session-fs_linux.go rename to pkg/configuration/session-fs_unix.go index 5363712..2dafa3e 100644 --- a/pkg/configuration/session-fs_linux.go +++ b/pkg/configuration/session-fs_unix.go @@ -1,4 +1,4 @@ -//go:build linux +//go:build unix package configuration diff --git a/pkg/configuration/user-requirement-template_unix.go b/pkg/configuration/user-requirement-template.go similarity index 99% rename from pkg/configuration/user-requirement-template_unix.go rename to pkg/configuration/user-requirement-template.go index 3f2a50c..48ccdde 100644 --- a/pkg/configuration/user-requirement-template_unix.go +++ b/pkg/configuration/user-requirement-template.go @@ -4,10 +4,12 @@ package configuration import ( "fmt" + + "gopkg.in/yaml.v3" + "github.com/engity-com/bifroest/pkg/common" "github.com/engity-com/bifroest/pkg/template" "github.com/engity-com/bifroest/pkg/user" - "gopkg.in/yaml.v3" ) type UserRequirementTemplate struct { diff --git a/pkg/configuration/user-requirement-template_windows.go b/pkg/configuration/user-requirement-template_windows.go deleted file mode 100644 index e0d95bd..0000000 --- a/pkg/configuration/user-requirement-template_windows.go +++ /dev/null @@ -1,86 +0,0 @@ -//go:build windows - -package configuration - -import ( - "fmt" - "github.com/engity-com/bifroest/pkg/common" - "github.com/engity-com/bifroest/pkg/template" - "github.com/engity-com/bifroest/pkg/user" - "gopkg.in/yaml.v3" -) - -var ( - DefaultUserRequirementShell = template.MustNewString(`cmd.exe`) -) - -type UserRequirementTemplate struct { - Name template.String `yaml:"name,omitempty"` - Uid *template.TextMarshaller[user.Id, *user.Id] `yaml:"uid,omitempty"` - Shell template.String `yaml:"shell,omitempty"` -} - -func (this *UserRequirementTemplate) SetDefaults() error { - return setDefaults(this, - noopSetDefault[UserRequirementTemplate]("name"), - noopSetDefault[UserRequirementTemplate]("uid"), - fixedDefault("shell", func(v *UserRequirementTemplate) *template.String { return &v.Shell }, DefaultUserRequirementShell), - ) -} - -func (this *UserRequirementTemplate) Trim() error { - return trim(this, - noopTrim[UserRequirementTemplate]("name"), - noopTrim[UserRequirementTemplate]("uid"), - noopTrim[UserRequirementTemplate]("shell"), - ) -} - -func (this *UserRequirementTemplate) Validate() error { - return validate(this, - notZeroValidate("name", func(v *UserRequirementTemplate) *template.String { return &v.Name }), - noopValidate[UserRequirementTemplate]("uid"), - notZeroValidate("shell", func(v *UserRequirementTemplate) *template.String { return &v.Shell }), - ) -} - -func (this *UserRequirementTemplate) UnmarshalYAML(node *yaml.Node) error { - return unmarshalYAML(this, node, func(target *UserRequirementTemplate, node *yaml.Node) error { - type raw UserRequirementTemplate - return node.Decode((*raw)(target)) - }) -} - -func (this UserRequirementTemplate) Render(key common.StructuredKey, data any) (_ *user.Requirement, err error) { - result := user.Requirement{} - if result.Name, err = this.Name.Render(data); err != nil { - return nil, fmt.Errorf("[%v] %w", key.Child("name"), err) - } - if v := this.Uid; v != nil { - buf, err := this.Uid.Render(data) - if err != nil { - return nil, fmt.Errorf("[%v] %w", key.Child("uid"), err) - } - result.Uid = &buf - } - return &result, nil -} - -func (this UserRequirementTemplate) IsEqualTo(other any) bool { - if other == nil { - return false - } - switch v := other.(type) { - case UserRequirementTemplate: - return this.isEqualTo(&v) - case *UserRequirementTemplate: - return this.isEqualTo(v) - default: - return false - } -} - -func (this UserRequirementTemplate) isEqualTo(other *UserRequirementTemplate) bool { - return isEqual(&this.Name, &other.Name) && - isEqual(this.Uid, other.Uid) -} diff --git a/pkg/environment/local-repository.go b/pkg/environment/local-repository.go index 04f8ec9..3e4f673 100644 --- a/pkg/environment/local-repository.go +++ b/pkg/environment/local-repository.go @@ -1,3 +1,5 @@ +//go:build unix + package environment import ( diff --git a/pkg/environment/local-token.go b/pkg/environment/local-token.go index e9512e4..4ab9824 100644 --- a/pkg/environment/local-token.go +++ b/pkg/environment/local-token.go @@ -1,6 +1,58 @@ +//go:build unix + package environment +import ( + "github.com/engity-com/bifroest/pkg/common" + "github.com/engity-com/bifroest/pkg/user" +) + type localToken struct { User localTokenUser `json:"user"` PortForwardingAllowed bool `json:"portForwardingAllowed"` } + +type localTokenUser struct { + Name string `json:"name,omitempty"` + Uid *user.Id `json:"uid,omitempty"` + Managed bool `json:"managed,omitempty"` + DeleteOnDispose bool `json:"deleteOnDispose,omitempty"` + DeleteHomeDirOnDispose bool `json:"deleteHomeDirOnDispose,omitempty"` + KillProcessesOnDispose bool `json:"killProcessesOnDispose,omitempty"` +} + +func (this *LocalRepository) newLocalToken(u *user.User, req Request, userIsManaged bool) (*localToken, error) { + fail := func(err error) (*localToken, error) { + return nil, err + } + + portForwardingAllowed, err := this.conf.PortForwardingAllowed.Render(req) + if err != nil { + return fail(err) + } + + deleteOnDispose, err := this.conf.Dispose.DeleteManagedUser.Render(req) + if err != nil { + return fail(err) + } + deleteHomeDirOnDispose, err := this.conf.Dispose.DeleteManagedUserHomeDir.Render(req) + if err != nil { + return fail(err) + } + killProcessesOnDispose, err := this.conf.Dispose.KillManagedUserProcesses.Render(req) + if err != nil { + return fail(err) + } + + return &localToken{ + localTokenUser{ + u.Name, + common.P(u.Uid), + userIsManaged, + deleteOnDispose && userIsManaged, + deleteHomeDirOnDispose && deleteOnDispose && userIsManaged, + killProcessesOnDispose && userIsManaged, + }, + portForwardingAllowed, + }, nil +} diff --git a/pkg/environment/local-token_linux.go b/pkg/environment/local-token_linux.go deleted file mode 100644 index 5773cf8..0000000 --- a/pkg/environment/local-token_linux.go +++ /dev/null @@ -1,53 +0,0 @@ -//go:build linux - -package environment - -import ( - "github.com/engity-com/bifroest/pkg/common" - "github.com/engity-com/bifroest/pkg/user" -) - -type localTokenUser struct { - Name string `json:"name,omitempty"` - Uid *user.Id `json:"uid,omitempty"` - Managed bool `json:"managed,omitempty"` - DeleteOnDispose bool `json:"deleteOnDispose,omitempty"` - DeleteHomeDirOnDispose bool `json:"deleteHomeDirOnDispose,omitempty"` - KillProcessesOnDispose bool `json:"killProcessesOnDispose,omitempty"` -} - -func (this *LocalRepository) newLocalToken(u *user.User, req Request, userIsManaged bool) (*localToken, error) { - fail := func(err error) (*localToken, error) { - return nil, err - } - - portForwardingAllowed, err := this.conf.PortForwardingAllowed.Render(req) - if err != nil { - return fail(err) - } - - deleteOnDispose, err := this.conf.Dispose.DeleteManagedUser.Render(req) - if err != nil { - return fail(err) - } - deleteHomeDirOnDispose, err := this.conf.Dispose.DeleteManagedUserHomeDir.Render(req) - if err != nil { - return fail(err) - } - killProcessesOnDispose, err := this.conf.Dispose.KillManagedUserProcesses.Render(req) - if err != nil { - return fail(err) - } - - return &localToken{ - localTokenUser{ - u.Name, - common.P(u.Uid), - userIsManaged, - deleteOnDispose && userIsManaged, - deleteHomeDirOnDispose && deleteOnDispose && userIsManaged, - killProcessesOnDispose && userIsManaged, - }, - portForwardingAllowed, - }, nil -} diff --git a/pkg/environment/local-token_windows.go b/pkg/environment/local-token_windows.go deleted file mode 100644 index f31e12b..0000000 --- a/pkg/environment/local-token_windows.go +++ /dev/null @@ -1,44 +0,0 @@ -//go:build windows - -package environment - -import ( - "fmt" - - "github.com/engity-com/bifroest/pkg/common" - "github.com/engity-com/bifroest/pkg/user" -) - -type localTokenUser struct { - Name string `json:"name,omitempty"` - Uid *user.Id `json:"uid,omitempty"` - Shell string `json:"shell,omitempty"` -} - -func (this *LocalRepository) newLocalToken(u *user.User, req Request, _ bool) (*localToken, error) { - fail := func(err error) (*localToken, error) { - return nil, err - } - failf := func(msg string, args ...any) (*localToken, error) { - return fail(fmt.Errorf(msg, args...)) - } - - portForwardingAllowed, err := this.conf.PortForwardingAllowed.Render(req) - if err != nil { - return fail(err) - } - - shell, err := this.conf.User.Shell.Render(req) - if err != nil { - return failf("cannot render user's shell: %w", err) - } - - return &localToken{ - localTokenUser{ - u.Name, - common.P(u.Uid), - shell, - }, - portForwardingAllowed, - }, nil -} diff --git a/pkg/environment/local.go b/pkg/environment/local.go index d0fc8e1..698c19e 100644 --- a/pkg/environment/local.go +++ b/pkg/environment/local.go @@ -1,3 +1,5 @@ +//go:build unix + package environment import ( @@ -7,6 +9,7 @@ import ( "net" "os" "os/exec" + "path/filepath" "strconv" "strings" "sync" @@ -22,8 +25,31 @@ import ( "github.com/engity-com/bifroest/pkg/errors" "github.com/engity-com/bifroest/pkg/session" "github.com/engity-com/bifroest/pkg/sys" + "github.com/engity-com/bifroest/pkg/user" ) +type local struct { + repository *LocalRepository + session session.Session + user *user.User + portForwardingAllowed bool + deleteUserOnDispose bool + deleteUserHomeDirOnDispose bool + killUserProcessesOnDispose bool +} + +func (this *LocalRepository) new(u *user.User, sess session.Session, portForwardingAllowed bool, lt *localToken) *local { + return &local{ + this, + sess, + u, + portForwardingAllowed, + lt.User.DeleteOnDispose, + lt.User.DeleteHomeDirOnDispose, + lt.User.KillProcessesOnDispose, + } +} + func (this *local) Session() session.Session { return this.session } @@ -66,10 +92,17 @@ func (this *local) Run(t Task) (exitCode int, rErr error) { return -1, fmt.Errorf("illegal task type: %v", t.TaskType()) } - this.configureEnvBefore(&ev) + if v, ok := os.LookupEnv("TZ"); ok { + ev.Set("TZ", v) + } ev.AddAllOf(t.Authorization().EnvVars()) ev.Add(sshSess.Environ()...) - this.configureEnvMid(&ev) + ev.Set( + "HOME", this.user.HomeDir, + "USER", this.user.Name, + "LOGNAME", this.user.Name, + "SHELL", this.user.Shell, + ) if ssh.AgentRequested(sshSess) { l, err := ssh.NewAgentListener() @@ -255,3 +288,75 @@ func (this *local) NewDestinationConnection(ctx context.Context, host string, po var dialer net.Dialer return dialer.DialContext(ctx, "tcp", dest) } + +func (this *local) configureShellCmd(t Task, cmd *exec.Cmd) error { + if rc := t.SshSession().RawCommand(); len(rc) > 0 { + cmd.Args = []string{filepath.Base(this.user.Shell), "-c", rc} + } else { + cmd.Args = []string{"-" + filepath.Base(this.user.Shell)} + } + return nil +} + +func (this *local) configureCmd(cmd *exec.Cmd) { + creds := this.user.ToCredentials() + cmd.SysProcAttr.Credential = &creds +} + +func (this *local) configureCmdForPty(cmd *exec.Cmd, pty, tty *os.File) error { + cmd.SysProcAttr.Setsid = true + cmd.SysProcAttr.Setctty = true + + if err := syscall.SetNonblock(int(pty.Fd()), true); err != nil { + return err + } + if err := syscall.SetNonblock(int(tty.Fd()), true); err != nil { + return err + } + return nil +} + +func (this *local) getPathEnv() string { + if v := os.Getenv("PATH"); v != "" { + return v + } + return "/bin;/usr/bin" +} + +func (this *local) signal(cmd *exec.Cmd, logger log.Logger, signal ssh.Signal) { + var sig sys.Signal + if err := sig.Set(string(signal)); err != nil { + sig = sys.SIGKILL + } + + if err := cmd.Process.Signal(sig.Native()); errors.Is(err, os.ErrProcessDone) { + // Ignored. + } else if err != nil { + logger.WithError(err). + With("pid", cmd.Process.Pid). + With("signal", sig). + Warn("cannot send signal to process") + } +} + +func (this *local) dispose(ctx context.Context) (bool, error) { + fail := func(err error) (bool, error) { + return false, err + } + + disposed := false + if this.deleteUserOnDispose { + if err := this.repository.userRepository.DeleteById(ctx, this.user.Uid, &user.DeleteOpts{ + HomeDir: common.P(this.deleteUserHomeDirOnDispose), + KillProcesses: common.P(this.killUserProcessesOnDispose), + }); errors.Is(err, user.ErrNoSuchUser) { + // Ok, continue.... + } else if err != nil { + return fail(err) + } else { + disposed = true + } + } + + return disposed, nil +} diff --git a/pkg/environment/local_linux.go b/pkg/environment/local_linux.go deleted file mode 100644 index 46e1b26..0000000 --- a/pkg/environment/local_linux.go +++ /dev/null @@ -1,129 +0,0 @@ -//go:build linux - -package environment - -import ( - "context" - "os" - "os/exec" - "path/filepath" - "syscall" - - log "github.com/echocat/slf4g" - "github.com/gliderlabs/ssh" - - "github.com/engity-com/bifroest/pkg/common" - "github.com/engity-com/bifroest/pkg/errors" - "github.com/engity-com/bifroest/pkg/session" - "github.com/engity-com/bifroest/pkg/sys" - "github.com/engity-com/bifroest/pkg/user" -) - -type local struct { - repository *LocalRepository - session session.Session - user *user.User - portForwardingAllowed bool - deleteUserOnDispose bool - deleteUserHomeDirOnDispose bool - killUserProcessesOnDispose bool -} - -func (this *LocalRepository) new(u *user.User, sess session.Session, portForwardingAllowed bool, lt *localToken) *local { - return &local{ - this, - sess, - u, - portForwardingAllowed, - lt.User.DeleteOnDispose, - lt.User.DeleteHomeDirOnDispose, - lt.User.KillProcessesOnDispose, - } -} - -func (this *local) configureShellCmd(t Task, cmd *exec.Cmd) error { - if rc := t.SshSession().RawCommand(); len(rc) > 0 { - cmd.Args = []string{filepath.Base(this.user.Shell), "-c", rc} - } else { - cmd.Args = []string{"-" + filepath.Base(this.user.Shell)} - } - return nil -} - -func (this *local) configureEnvBefore(ev *sys.EnvVars) { - if v, ok := os.LookupEnv("TZ"); ok { - ev.Set("TZ", v) - } -} - -func (this *local) configureEnvMid(ev *sys.EnvVars) { - ev.Set( - "HOME", this.user.HomeDir, - "USER", this.user.Name, - "LOGNAME", this.user.Name, - "SHELL", this.user.Shell, - ) -} - -func (this *local) configureCmd(cmd *exec.Cmd) { - creds := this.user.ToCredentials() - cmd.SysProcAttr.Credential = &creds -} - -func (this *local) configureCmdForPty(cmd *exec.Cmd, pty, tty *os.File) error { - cmd.SysProcAttr.Setsid = true - cmd.SysProcAttr.Setctty = true - - if err := syscall.SetNonblock(int(pty.Fd()), true); err != nil { - return err - } - if err := syscall.SetNonblock(int(tty.Fd()), true); err != nil { - return err - } - return nil -} - -func (this *local) getPathEnv() string { - if v := os.Getenv("PATH"); v != "" { - return v - } - return "/bin;/usr/bin" -} - -func (this *local) signal(cmd *exec.Cmd, logger log.Logger, signal ssh.Signal) { - var sig sys.Signal - if err := sig.Set(string(signal)); err != nil { - sig = sys.SIGKILL - } - - if err := cmd.Process.Signal(sig.Native()); errors.Is(err, os.ErrProcessDone) { - // Ignored. - } else if err != nil { - logger.WithError(err). - With("pid", cmd.Process.Pid). - With("signal", sig). - Warn("cannot send signal to process") - } -} - -func (this *local) dispose(ctx context.Context) (bool, error) { - fail := func(err error) (bool, error) { - return false, err - } - - disposed := false - if this.deleteUserOnDispose { - if err := this.repository.userRepository.DeleteById(ctx, this.user.Uid, &user.DeleteOpts{ - HomeDir: common.P(this.deleteUserHomeDirOnDispose), - KillProcesses: common.P(this.killUserProcessesOnDispose), - }); errors.Is(err, user.ErrNoSuchUser) { - // Ok, continue.... - } else if err != nil { - return fail(err) - } else { - disposed = true - } - } - - return disposed, nil -} diff --git a/pkg/environment/local_windows.go b/pkg/environment/local_windows.go deleted file mode 100644 index e49c694..0000000 --- a/pkg/environment/local_windows.go +++ /dev/null @@ -1,92 +0,0 @@ -//go:build windows - -package environment - -import ( - "context" - "os" - "os/exec" - "syscall" - - log "github.com/echocat/slf4g" - "github.com/gliderlabs/ssh" - - "github.com/engity-com/bifroest/pkg/errors" - "github.com/engity-com/bifroest/pkg/session" - "github.com/engity-com/bifroest/pkg/sys" - "github.com/engity-com/bifroest/pkg/user" -) - -type local struct { - repository *LocalRepository - session session.Session - user *user.User - portForwardingAllowed bool - shell string -} - -func (this *LocalRepository) new(u *user.User, sess session.Session, portForwardingAllowed bool, lt *localToken) *local { - return &local{ - this, - sess, - u, - portForwardingAllowed, - lt.User.Shell, - } -} - -func (this *local) configureShellCmd(_ Task, cmd *exec.Cmd) error { - shell, err := exec.LookPath(this.shell) - if err != nil { - return errors.Config.Newf("configured shell %q cannot be resolved: %w", this.shell, err) - } - - cmd.Path = shell - cmd.Args = []string{shell} - return nil -} - -func (this *local) configureEnvBefore(_ *sys.EnvVars) {} - -func (this *local) configureEnvMid(_ *sys.EnvVars) {} - -func (this *local) configureCmd(_ *exec.Cmd) { - //cmd.SysProcAttr.NoInheritHandles = true -} - -func (this *local) configureCmdForPty(_ *exec.Cmd, pty, tty *os.File) error { - if err := syscall.SetNonblock(syscall.Handle(int(pty.Fd())), true); err != nil { - return err - } - if err := syscall.SetNonblock(syscall.Handle(int(tty.Fd())), true); err != nil { - return err - } - return nil -} - -func (this *local) getPathEnv() string { - if v := os.Getenv("PATH"); v != "" { - return v - } - return `C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem` -} - -func (this *local) signal(cmd *exec.Cmd, logger log.Logger, signal ssh.Signal) { - var sig sys.Signal - if err := sig.Set(string(signal)); err != nil { - sig = sys.SIGKILL - } - - if err := cmd.Process.Signal(sig.Native()); errors.Is(err, os.ErrProcessDone) { - // Ignored. - } else if err != nil { - logger.WithError(err). - With("pid", cmd.Process.Pid). - With("signal", sig). - Warn("cannot send signal to process") - } -} - -func (this *local) dispose(_ context.Context) (bool, error) { - return true, nil -} diff --git a/pkg/service/service-direct-tcp-ip.go b/pkg/service/service-direct-tcp-ip.go index 3a0ef32..187f463 100644 --- a/pkg/service/service-direct-tcp-ip.go +++ b/pkg/service/service-direct-tcp-ip.go @@ -4,6 +4,7 @@ import ( "io" "sync" "sync/atomic" + "syscall" "time" "github.com/gliderlabs/ssh" @@ -11,6 +12,7 @@ import ( "github.com/engity-com/bifroest/pkg/common" "github.com/engity-com/bifroest/pkg/environment" + "github.com/engity-com/bifroest/pkg/errors" ) type localForwardChannelData struct { @@ -147,3 +149,21 @@ func (this *service) onReversePortForwardingRequested(_ ssh.Context, _ string, _ // TODO! Maybe more checks here in the future? return true } + +func (this *service) isAcceptableNewConnectionError(err error) bool { + if err == nil { + return false + } + + var sce syscall.Errno + if errors.As(err, &sce) { + switch sce { + case syscall.ECONNREFUSED, syscall.ETIMEDOUT, syscall.EHOSTDOWN, syscall.ENETUNREACH: + return true + default: + return false + } + } + + return false +} diff --git a/pkg/service/service_linux.go b/pkg/service/service_linux.go deleted file mode 100644 index 88172fe..0000000 --- a/pkg/service/service_linux.go +++ /dev/null @@ -1,25 +0,0 @@ -//go:build linux - -package service - -import ( - "syscall" - - "github.com/engity-com/bifroest/pkg/errors" -) - -func (this *service) isAcceptableNewConnectionError(err error) bool { - if err == nil { - return false - } - - var sce syscall.Errno - if errors.As(err, &sce) { - switch sce { - case syscall.ECONNREFUSED, syscall.ETIMEDOUT, syscall.EHOSTDOWN, syscall.ENETUNREACH: - return true - } - } - - return false -} diff --git a/pkg/service/service_windows.go b/pkg/service/service_windows.go deleted file mode 100644 index 5ffe16f..0000000 --- a/pkg/service/service_windows.go +++ /dev/null @@ -1,27 +0,0 @@ -//go:build windows - -package service - -import ( - "syscall" - - "github.com/engity-com/bifroest/pkg/errors" -) - -func (this *service) isAcceptableNewConnectionError(err error) bool { - if err == nil { - return false - } - - var sce syscall.Errno - if errors.As(err, &sce) { - switch sce { - case syscall.ECONNREFUSED, syscall.ETIMEDOUT, syscall.EHOSTDOWN, syscall.ENETUNREACH: - return true - default: - return false - } - } - - return false -} diff --git a/pkg/sys/signal_linux.go b/pkg/sys/signal_unix.go similarity index 99% rename from pkg/sys/signal_linux.go rename to pkg/sys/signal_unix.go index a978dd4..8b48de7 100644 --- a/pkg/sys/signal_linux.go +++ b/pkg/sys/signal_unix.go @@ -1,4 +1,4 @@ -//go:build linux +//go:build unix package sys diff --git a/pkg/user/etc-colon-entry_test.go b/pkg/user/etc-colon-entry_test.go index 96a4313..653f314 100644 --- a/pkg/user/etc-colon-entry_test.go +++ b/pkg/user/etc-colon-entry_test.go @@ -1,4 +1,4 @@ -//go:build test +//go:build unix package user diff --git a/pkg/user/etc-colon-repository-handle.go b/pkg/user/etc-colon-repository-handle.go index b7a981e..b96c576 100644 --- a/pkg/user/etc-colon-repository-handle.go +++ b/pkg/user/etc-colon-repository-handle.go @@ -1,4 +1,4 @@ -//go:build linux +//go:build unix package user diff --git a/pkg/user/etc-colon-repository-handles.go b/pkg/user/etc-colon-repository-handles.go index 4db945e..fc1aef7 100644 --- a/pkg/user/etc-colon-repository-handles.go +++ b/pkg/user/etc-colon-repository-handles.go @@ -1,4 +1,4 @@ -//go:build linux +//go:build unix package user diff --git a/pkg/user/etc-colon-repository_test.go b/pkg/user/etc-colon-repository_test.go index 8630397..611893c 100644 --- a/pkg/user/etc-colon-repository_test.go +++ b/pkg/user/etc-colon-repository_test.go @@ -1,4 +1,4 @@ -//go:build linux +//go:build unix package user diff --git a/pkg/user/group-requirement_windows.go b/pkg/user/group-requirement_windows.go deleted file mode 100644 index 8b6b881..0000000 --- a/pkg/user/group-requirement_windows.go +++ /dev/null @@ -1,5 +0,0 @@ -//go:build windows - -package user - -type GroupRequirement struct{} diff --git a/pkg/user/group_windows.go b/pkg/user/group_windows.go deleted file mode 100644 index 96887bd..0000000 --- a/pkg/user/group_windows.go +++ /dev/null @@ -1,58 +0,0 @@ -//go:build windows - -package user - -import ( - "fmt" -) - -type Group struct { - Gid GroupId - Name string -} - -func (this Group) GetField(name string) (any, bool, error) { - switch name { - case "name": - return this.Name, true, nil - case "gid": - return this.Gid, true, nil - default: - return nil, false, fmt.Errorf("unknown field %q", name) - } -} - -func (this Group) String() string { - return fmt.Sprintf("%v(%s)", this.Gid, this.Name) -} - -func (this Group) IsEqualTo(other any) bool { - if other == nil { - return false - } - switch v := other.(type) { - case Group: - return this.isEqualTo(&v) - case *Group: - return this.isEqualTo(v) - default: - return false - } -} - -func (this Group) isEqualTo(other *Group) bool { - return this.Gid == other.Gid && - this.Name == other.Name -} - -type GroupId string - -func GroupIdEqualsP(a, b *GroupId) bool { - if a == nil && b == nil { - return true - } - if a == nil || b == nil { - return false - } - return *a == *b -} diff --git a/pkg/user/requirement_windows.go b/pkg/user/requirement_windows.go deleted file mode 100644 index f1c11f6..0000000 --- a/pkg/user/requirement_windows.go +++ /dev/null @@ -1,54 +0,0 @@ -//go:build windows - -package user - -import ( - "fmt" - "strings" -) - -type Requirement struct { - Name string `yaml:"name,omitempty"` - Uid *Id `yaml:"uid,omitempty"` - Shell string `yaml:"strign,omitempty"` -} - -func (this Requirement) IsZero() bool { - return len(this.Name) == 0 && - this.Uid == nil && - this.Shell == "" -} - -func (this Requirement) IsEqualTo(other any) bool { - if other == nil { - return false - } - switch v := other.(type) { - case Requirement: - return this.isEqualTo(&v) - case *Requirement: - return this.isEqualTo(v) - default: - return false - } -} - -func (this Requirement) isEqualTo(other *Requirement) bool { - return this.Name == other.Name && - IdEqualsP(this.Uid, other.Uid) && - this.Shell == other.Shell -} - -func (this Requirement) String() string { - if name := this.Name; len(name) > 0 { - if uid := this.Uid; uid != nil { - return fmt.Sprintf("%v(%s)", uid, name) - } else { - return strings.Clone(name) - } - } else if uid := this.Uid; uid != nil { - return uid.String() - } else { - return "" - } -} diff --git a/pkg/user/user_windows.go b/pkg/user/user_windows.go deleted file mode 100644 index 4306c21..0000000 --- a/pkg/user/user_windows.go +++ /dev/null @@ -1,133 +0,0 @@ -//go:build windows - -package user - -import ( - "bytes" - "fmt" - "syscall" -) - -type User struct { - Name string - DisplayName string - Uid Id - HomeDir string -} - -func (this User) GetField(name string) (any, bool, error) { - switch name { - case "name": - return this.Name, true, nil - case "displayName": - return this.DisplayName, true, nil - case "uid": - return this.Uid, true, nil - case "homeDir": - return this.HomeDir, true, nil - default: - return nil, false, fmt.Errorf("unknown field %q", name) - } -} - -func (this User) String() string { - return fmt.Sprintf("%v(%s)", this.Uid, this.Name) -} - -func (this User) IsEqualTo(other any) bool { - if other == nil { - return false - } - switch v := other.(type) { - case User: - return this.isEqualTo(&v) - case *User: - return this.isEqualTo(v) - default: - return false - } -} - -func (this User) isEqualTo(other *User) bool { - return this.Name == other.Name && - this.DisplayName == other.DisplayName && - this.Uid == other.Uid && - this.HomeDir == other.HomeDir -} - -type Id struct { - *syscall.SID -} - -func (this Id) MarshalText() (text []byte, err error) { - sid := this.SID - if sid == nil { - return nil, nil - } - v, err := sid.String() - if err != nil { - return nil, err - } - return []byte(v), nil -} - -func (this *Id) UnmarshalText(text []byte) error { - buf, err := syscall.StringToSid(string(text)) - if err != nil { - return fmt.Errorf("illegal user id: %v", string(text)) - } - *this = Id{buf} - return nil -} - -func (this *Id) Set(plain string) error { - return this.UnmarshalText([]byte(plain)) -} - -func (this Id) String() string { - v, err := this.MarshalText() - if err != nil { - return fmt.Sprintf("ERR: %v", err) - } - return string(v) -} - -func (this Id) IsEqualTo(other any) bool { - if other == nil { - return false - } - switch v := other.(type) { - case Id: - return this.isEqualTo(&v) - case *Id: - return this.isEqualTo(v) - default: - return false - } -} - -func (this Id) isEqualTo(other *Id) bool { - if other == nil { - return false - } - if this.SID == nil && other.SID == nil { - return true - } - if this.SID == nil || other.SID == nil { - return false - } - tv, tErr := this.MarshalText() - ov, oErr := other.MarshalText() - return bytes.Equal(tv, ov) && - tErr == oErr -} - -func IdEqualsP(a, b *Id) bool { - if a == nil && b == nil { - return true - } - if a == nil || b == nil { - return false - } - return a.isEqualTo(b) -} diff --git a/pkg/user/windows_repository.go b/pkg/user/windows_repository.go deleted file mode 100644 index 7c19d15..0000000 --- a/pkg/user/windows_repository.go +++ /dev/null @@ -1,133 +0,0 @@ -//go:build windows - -package user - -import ( - "context" - "fmt" - "os/user" - - "golang.org/x/sys/windows" - - "github.com/engity-com/bifroest/pkg/errors" -) - -func init() { - DefaultRepositoryProvider = &SharedRepositoryProvider[*WindowsRepository]{V: &WindowsRepository{}} -} - -type WindowsRepository struct { -} - -func (this *WindowsRepository) Init(_ context.Context) error { - return nil -} - -func (this *WindowsRepository) Close() error { - return nil -} - -func (this *WindowsRepository) Ensure(ctx context.Context, req *Requirement, _ *EnsureOpts) (u *User, res EnsureResult, err error) { - name := req.Name - uid := req.Uid - - if name != "" { - u, err = this.LookupByName(ctx, name) - if err != nil { - return nil, EnsureResultError, err - } - if uid != nil && !uid.isEqualTo(&u.Uid) { - return nil, EnsureResultError, ErrUserDoesNotFulfilRequirement - } - return u, EnsureResultUnchanged, nil - } - - if uid != nil { - u, err = this.LookupById(ctx, *uid) - if err != nil { - return nil, EnsureResultError, err - } - return u, EnsureResultUnchanged, nil - - } - - return nil, EnsureResultError, fmt.Errorf("required does neither define name nor UID") -} - -func (this *WindowsRepository) EnsureGroup(context.Context, *GroupRequirement, *EnsureOpts) (*Group, EnsureResult, error) { - return nil, EnsureResultError, ErrGroupDoesNotFulfilRequirement -} - -func (this *WindowsRepository) LookupByName(_ context.Context, name string) (*User, error) { - u, err := user.Lookup(name) - if errors.Is(err, windows.ERROR_NONE_MAPPED) || errors.Is(err, (*user.UnknownUserIdError)(nil)) { - return nil, ErrNoSuchUser - } - if err != nil { - return nil, err - } - var id Id - if err := id.UnmarshalText([]byte(u.Uid)); err != nil { - return nil, err - } - - return &User{ - Name: u.Username, - DisplayName: u.Name, - Uid: id, - HomeDir: u.HomeDir, - }, nil -} - -func (this *WindowsRepository) LookupById(_ context.Context, id Id) (*User, error) { - strId, err := id.MarshalText() - if err != nil { - return nil, err - } - u, err := user.LookupId(string(strId)) - if errors.Is(err, windows.ERROR_NONE_MAPPED) || errors.Is(err, (*user.UnknownUserIdError)(nil)) { - return nil, ErrNoSuchUser - } - if err != nil { - return nil, err - } - - return &User{ - Name: u.Username, - DisplayName: u.Name, - Uid: id, - HomeDir: u.HomeDir, - }, nil -} - -func (this *WindowsRepository) LookupGroupByName(context.Context, string) (*Group, error) { - return nil, ErrNoSuchGroup -} - -func (this *WindowsRepository) LookupGroupById(context.Context, GroupId) (*Group, error) { - return nil, ErrNoSuchGroup -} - -func (this *WindowsRepository) DeleteById(context.Context, Id, *DeleteOpts) error { - return fmt.Errorf("delete not supported on windows systems") -} - -func (this *WindowsRepository) DeleteByName(context.Context, string, *DeleteOpts) error { - return fmt.Errorf("delete not supported on windows systems") -} - -func (this *WindowsRepository) ValidatePasswordById(context.Context, Id, string) (bool, error) { - return false, fmt.Errorf("validate password not supported on windows systems") -} - -func (this *WindowsRepository) ValidatePasswordByName(context.Context, string, string) (bool, error) { - return false, fmt.Errorf("validate password not supported on windows systems") -} - -func (this *WindowsRepository) DeleteGroupById(context.Context, GroupId, *DeleteOpts) error { - return fmt.Errorf("delete not supported on windows systems") -} - -func (this *WindowsRepository) DeleteGroupByName(context.Context, string, *DeleteOpts) error { - return fmt.Errorf("delete not supported on windows systems") -} diff --git a/pkg/user/windows_repository_test.go b/pkg/user/windows_repository_test.go deleted file mode 100644 index c1199b3..0000000 --- a/pkg/user/windows_repository_test.go +++ /dev/null @@ -1,234 +0,0 @@ -//go:build windows - -package user - -import ( - "context" - "os/user" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/engity-com/bifroest/pkg/common" -) - -func TestWindowsRepository_Lifecycle(t *testing.T) { - instance := &WindowsRepository{} - - actualErr := instance.Init(context.Background()) - require.NoError(t, actualErr) - - actualErr = instance.Close() - require.NoError(t, actualErr) -} - -func TestWindowsRepository_DeleteById(t *testing.T) { - instance := newWindowsRepository(t) - id := sidOf(t, "S-1-1-21-1") - - actualErr := instance.DeleteById(context.Background(), id, nil) - assert.ErrorContains(t, actualErr, "delete not supported on windows systems") -} - -func TestWindowsRepository_DeleteByName(t *testing.T) { - instance := newWindowsRepository(t) - - actualErr := instance.DeleteByName(context.Background(), "test", nil) - assert.ErrorContains(t, actualErr, "delete not supported on windows systems") -} - -func TestWindowsRepository_DeleteGroupById(t *testing.T) { - instance := newWindowsRepository(t) - - actualErr := instance.DeleteGroupById(context.Background(), "test", nil) - assert.ErrorContains(t, actualErr, "delete not supported on windows systems") -} - -func TestWindowsRepository_DeleteGroupByName(t *testing.T) { - instance := newWindowsRepository(t) - - actualErr := instance.DeleteGroupByName(context.Background(), "test", nil) - assert.ErrorContains(t, actualErr, "delete not supported on windows systems") -} - -func TestWindowsRepository_Ensure(t *testing.T) { - instance := newWindowsRepository(t) - current := currentUser(t) - currentAsUser := User{ - Name: current.Username, - DisplayName: current.Name, - Uid: uidOfUser(t, current), - HomeDir: current.HomeDir, - } - - cases := []struct { - name string - requirement Requirement - expected *User - expectedRes EnsureResult - expectedErr string - }{{ - name: "by-name-exists", - requirement: Requirement{ - Name: current.Username, - }, - expected: ¤tAsUser, - expectedRes: EnsureResultUnchanged, - }, { - name: "by-id-exists", - requirement: Requirement{ - Uid: common.P(uidOfUser(t, current)), - }, - expected: ¤tAsUser, - expectedRes: EnsureResultUnchanged, - }, { - name: "by-both-exists", - requirement: Requirement{ - Name: current.Username, - Uid: common.P(uidOfUser(t, current)), - }, - expected: ¤tAsUser, - expectedRes: EnsureResultUnchanged, - }, { - name: "by-both-but-different", - requirement: Requirement{ - Name: current.Username, - Uid: common.P(sidOf(t, "S-1-1-21-1")), - }, - expectedRes: EnsureResultError, - expectedErr: ErrUserDoesNotFulfilRequirement.Error(), - }, { - name: "by-name-absent", - requirement: Requirement{ - Name: "does not exist", - }, - expectedRes: EnsureResultError, - expectedErr: ErrNoSuchUser.Error(), - }, { - name: "by-id-absent", - requirement: Requirement{ - Uid: common.P(sidOf(t, "S-1-1-21-1")), - }, - expectedRes: EnsureResultError, - expectedErr: ErrNoSuchUser.Error(), - }, { - name: "by-nothing", - requirement: Requirement{}, - expectedRes: EnsureResultError, - expectedErr: "required does neither define name nor UID", - }} - - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - actual, actualRes, actualErr := instance.Ensure(context.Background(), &c.requirement, nil) - if c.expectedErr != "" { - require.EqualError(t, actualErr, c.expectedErr) - } else { - require.NoError(t, actualErr) - } - assert.Equal(t, c.expectedRes, actualRes) - assert.Equal(t, c.expected, actual) - }) - } -} - -func TestWindowsRepository_EnsureGroup(t *testing.T) { - instance := newWindowsRepository(t) - - actual, actualRes, actualErr := instance.EnsureGroup(context.Background(), &GroupRequirement{}, nil) - assert.ErrorIs(t, actualErr, ErrGroupDoesNotFulfilRequirement) - assert.Equal(t, EnsureResultError, actualRes) - assert.Nil(t, actual) -} - -func TestWindowsRepository_LookupByName(t *testing.T) { - instance := newWindowsRepository(t) - - current := currentUser(t) - actual, actualErr := instance.LookupByName(context.Background(), current.Username) - require.NoError(t, actualErr) - - assert.NotNil(t, actual) - assert.Equal(t, current.Username, actual.Name) - assert.Equal(t, current.Uid, actual.Uid.String()) - assert.Equal(t, current.HomeDir, actual.HomeDir) -} - -func TestWindowsRepository_LookupById(t *testing.T) { - instance := newWindowsRepository(t) - - current := currentUser(t) - actual, actualErr := instance.LookupById(context.Background(), uidOfUser(t, current)) - require.NoError(t, actualErr) - - assert.NotNil(t, actual) - assert.Equal(t, current.Username, actual.Name) - assert.Equal(t, current.Uid, actual.Uid.String()) - assert.Equal(t, current.HomeDir, actual.HomeDir) -} - -func TestWindowsRepository_LookupGroupById(t *testing.T) { - instance := newWindowsRepository(t) - - actual, actualErr := instance.LookupGroupById(context.Background(), "123") - assert.ErrorIs(t, actualErr, ErrNoSuchGroup) - assert.Nil(t, actual) -} - -func TestWindowsRepository_LookupGroupByName(t *testing.T) { - instance := newWindowsRepository(t) - - actual, actualErr := instance.LookupGroupByName(context.Background(), "123") - assert.ErrorIs(t, actualErr, ErrNoSuchGroup) - assert.Nil(t, actual) -} - -func TestWindowsRepository_ValidatePasswordById(t *testing.T) { - instance := newWindowsRepository(t) - uid := currentUid(t) - - actual, actualErr := instance.ValidatePasswordById(context.Background(), uid, "test") - assert.ErrorContains(t, actualErr, "validate password not supported on windows systems") - assert.False(t, actual) -} - -func TestWindowsRepository_ValidatePasswordByName(t *testing.T) { - instance := newWindowsRepository(t) - - actual, actualErr := instance.ValidatePasswordByName(context.Background(), "demo132", "A2efh#fA$k^9o") - assert.ErrorContains(t, actualErr, "validate password not supported on windows systems") - assert.False(t, actual) - -} - -func currentUid(t *testing.T) Id { - current := currentUser(t) - - return uidOfUser(t, current) -} - -func uidOfUser(t *testing.T, v *user.User) Id { - return sidOf(t, v.Uid) -} - -func sidOf(t *testing.T, plain string) Id { - var sid Id - require.NoError(t, sid.Set(plain)) - return sid -} - -func newWindowsRepository(t *testing.T) *WindowsRepository { - instance := &WindowsRepository{} - assert.NoError(t, instance.Init(context.Background())) - t.Cleanup(func() { - _ = instance.Close() - }) - return instance -} - -func currentUser(t *testing.T) *user.User { - current, err := user.Current() - require.NoError(t, err) - return current -}