From 746cf06e13cb0325fd2849c0fd51133671de26b5 Mon Sep 17 00:00:00 2001 From: Ryo Ota Date: Wed, 9 Aug 2023 09:01:34 +0900 Subject: [PATCH 01/13] dependabot: `package-ecosystem: github-actions` --- .github/dependabot.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index aa5064f..3dc957c 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -10,3 +10,11 @@ updates: - nwtgck assignees: - nwtgck +- package-ecosystem: github-actions + directory: "/" + schedule: + interval: daily + timezone: Asia/Tokyo + open-pull-requests-limit: 99 + reviewers: [ nwtgck ] + assignees: [ nwtgck ] From ece9017869c5295367e12938e2b015e14c58aa35 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 9 Aug 2023 09:03:34 +0900 Subject: [PATCH 02/13] build(deps): bump actions/checkout from 2 to 3 (#2) Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v2...v3) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- .github/workflows/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8632df3..8894262 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,7 +6,7 @@ jobs: build_multi_platform: runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Go 1.x uses: actions/setup-go@v2 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 86c3c28..132e87d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,7 +9,7 @@ jobs: goreleaser: runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: fetch-depth: 0 - name: Set up Go From 86554a2289fdc5af9179281d8109740c5fac62f5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 9 Aug 2023 09:03:51 +0900 Subject: [PATCH 03/13] build(deps): bump actions/setup-go from 2 to 4 (#3) Bumps [actions/setup-go](https://github.com/actions/setup-go) from 2 to 4. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/v2...v4) --- updated-dependencies: - dependency-name: actions/setup-go dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- .github/workflows/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8894262..883a903 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up Go 1.x - uses: actions/setup-go@v2 + uses: actions/setup-go@v4 with: go-version: "1.20" - name: Build for multi-platform diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 132e87d..24e6983 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,7 +13,7 @@ jobs: with: fetch-depth: 0 - name: Set up Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v4 with: go-version: "1.20" - name: Run GoReleaser From 24a4ed687118690c5f593af0347c29fdec6430d8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 9 Aug 2023 09:17:41 +0900 Subject: [PATCH 04/13] build(deps): bump goreleaser/goreleaser-action from 2 to 4 (#1) Bumps [goreleaser/goreleaser-action](https://github.com/goreleaser/goreleaser-action) from 2 to 4. - [Release notes](https://github.com/goreleaser/goreleaser-action/releases) - [Commits](https://github.com/goreleaser/goreleaser-action/compare/v2...v4) --- updated-dependencies: - dependency-name: goreleaser/goreleaser-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 24e6983..1b6ad7d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,7 +17,7 @@ jobs: with: go-version: "1.20" - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v2 + uses: goreleaser/goreleaser-action@v4 with: version: v1.3.1 args: release --rm-dist From 26baea535cb98d3adf5b3ce891cb1aa3b085abb1 Mon Sep 17 00:00:00 2001 From: Ryo Ota Date: Wed, 9 Aug 2023 09:19:27 +0900 Subject: [PATCH 05/13] docs: "--help" --- README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.md b/README.md index be55969..16b8b62 100644 --- a/README.md +++ b/README.md @@ -22,3 +22,21 @@ handy-sshd -p 2222 --user "john:" --user "alice:" # Listen on unix domain socket handy-sshd --unix-socket /tmp/my-unix-socket --user "john:" ``` + +## --help + +```bash +Portable SSH server + +Usage: + handy-sshd [flags] + +Flags: + -h, --help help for handy-sshd + --host string SSH server host (e.g. 127.0.0.1) + -p, --port uint16 SSH server port (default 2222) + --shell string Shell + --unix-socket string Unix-domain socket + --user stringArray SSH user name (e.g. "john:mypassword") + -v, --version show version +``` From c4cc2752d393a5aad454e0d97f3ba6b18b74c5c7 Mon Sep 17 00:00:00 2001 From: Ryo Ota Date: Wed, 9 Aug 2023 09:28:04 +0900 Subject: [PATCH 06/13] docs: add install sections --- README.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 16b8b62..036c28a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,20 @@ # handy-sshd -Portable SSH Server +Portable SSH Server + +## Install on Ubuntu/Debian + +```bash +wget https://github.com/nwtgck/handy-sshd/releases/download/v0.1.0/handy-sshd-0.1.0-linux-amd64.deb +sudo dpkg -i handy-sshd-0.1.0-linux-amd64.deb +``` + +## Install on Mac + +```bash +brew install nwtgck/handy-sshd/handy-sshd +``` + +Get more executables in [the releases](https://github.com/nwtgck/handy-sshd/releases). ## Examples From 0796e5e2d9f46b5ce49ed0d37d5916de2c641ab7 Mon Sep 17 00:00:00 2001 From: Ryo Ota Date: Wed, 9 Aug 2023 20:36:19 +0900 Subject: [PATCH 07/13] docs: add badge and chore --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 036c28a..849cc70 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ # handy-sshd +[![CI](https://github.com/nwtgck/handy-sshd/actions/workflows/ci.yml/badge.svg)](https://github.com/nwtgck/handy-sshd/actions/workflows/ci.yml) + Portable SSH Server ## Install on Ubuntu/Debian @@ -40,7 +42,7 @@ handy-sshd --unix-socket /tmp/my-unix-socket --user "john:" ## --help -```bash +``` Portable SSH server Usage: From 1143b17071b3217c2df032401eb77842630875f3 Mon Sep 17 00:00:00 2001 From: Ryo Ota Date: Wed, 9 Aug 2023 20:45:46 +0900 Subject: [PATCH 08/13] implement permissions --- cmd/root.go | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++-- server.go | 38 ++++++++++++++++++++++++++++-- 2 files changed, 100 insertions(+), 4 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index c964a03..bbd5324 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -21,6 +21,21 @@ var flag struct { sshUnixSocket string sshShell string sshUsers []string + + allowTcpipForward bool + allowDirectTcpip bool + allowExecute bool + allowSftp bool +} + +var allPermissionFlags = []struct { + name string + flagPtr *bool +}{ + {name: "tcpip-forward", flagPtr: &flag.allowTcpipForward}, + {name: "direct-tcpip", flagPtr: &flag.allowDirectTcpip}, + {name: "execute", flagPtr: &flag.allowExecute}, + {name: "sftp", flagPtr: &flag.allowSftp}, } type sshUser struct { @@ -38,6 +53,12 @@ func init() { RootCmd.PersistentFlags().StringVarP(&flag.sshShell, "shell", "", "", "Shell") //RootCmd.PersistentFlags().StringVar(&flag.dnsServer, "dns-server", "", "DNS server (e.g. 1.1.1.1:53)") RootCmd.PersistentFlags().StringArrayVarP(&flag.sshUsers, "user", "", nil, `SSH user name (e.g. "john:mypassword")`) + + // Permission flags + RootCmd.PersistentFlags().BoolVarP(&flag.allowTcpipForward, "allow-tcpip-forward", "", false, "client can use remote forwarding") + RootCmd.PersistentFlags().BoolVarP(&flag.allowDirectTcpip, "allow-direct-tcpip", "", false, "client can use local forwarding and SOCKS proxy") + RootCmd.PersistentFlags().BoolVarP(&flag.allowExecute, "allow-execute", "", false, "client can use shell/interactive shell") + RootCmd.PersistentFlags().BoolVarP(&flag.allowSftp, "allow-sftp", "", false, "client can use SFTP and SSHFS") } var RootCmd = &cobra.Command{ @@ -51,8 +72,26 @@ var RootCmd = &cobra.Command{ return nil } logger := slog.Default() + + // Allow all permissions if all permission is not set + { + allPermissionFalse := true + for _, permissionFlag := range allPermissionFlags { + allPermissionFalse = allPermissionFalse && !*permissionFlag.flagPtr + } + if allPermissionFalse { + for _, permissionFlag := range allPermissionFlags { + *permissionFlag.flagPtr = true + } + } + } + sshServer := &handy_sshd.Server{ - Logger: logger, + Logger: logger, + AllowTcpipForward: flag.allowTcpipForward, + AllowDirectTcpip: flag.allowDirectTcpip, + AllowExecute: flag.allowExecute, + AllowSftp: flag.allowSftp, } var sshUsers []sshUser for _, u := range flag.sshUsers { @@ -109,6 +148,9 @@ var RootCmd = &cobra.Command{ logger.Info(fmt.Sprintf("listening on %s...", flag.sshUnixSocket)) } defer ln.Close() + + showPermissions(logger) + for { conn, err := ln.Accept() if err != nil { @@ -121,9 +163,29 @@ var RootCmd = &cobra.Command{ conn.Close() continue } - logger.Info("new SSH connection", "client_version", string(sshConn.ClientVersion())) + logger.Info("new SSH connection", "remote_address", sshConn.RemoteAddr(), "client_version", string(sshConn.ClientVersion())) go sshServer.HandleGlobalRequests(sshConn, reqs) go sshServer.HandleChannels(flag.sshShell, chans) } }, } + +func showPermissions(logger *slog.Logger) { + var allowedList []string + var notAllowedList []string + for _, permissionFlag := range allPermissionFlags { + if *permissionFlag.flagPtr { + allowedList = append(allowedList, `"`+permissionFlag.name+`"`) + } else { + notAllowedList = append(notAllowedList, `"`+permissionFlag.name+`"`) + } + } + showList := func(l []string) string { + if len(l) == 0 { + return "none" + } + return strings.Join(l, ", ") + } + logger.Info(fmt.Sprintf("allowed: %s", showList(allowedList))) + logger.Info(fmt.Sprintf("NOT allowed: %s", showList(notAllowedList))) +} diff --git a/server.go b/server.go index 1ede795..f8f514b 100644 --- a/server.go +++ b/server.go @@ -28,6 +28,13 @@ import ( type Server struct { Logger *slog.Logger + + // Permissions + AllowTcpipForward bool + AllowDirectTcpip bool + AllowExecute bool // this should not be split into "allow-exec" and "allow-pty-req" for now because "pty-req" can be used not for shell execution. + AllowSftp bool + // TODO: DNS server ? } @@ -47,6 +54,10 @@ func (s *Server) handleChannel(shell string, newChannel ssh.NewChannel) { case "session": s.handleSession(shell, newChannel) case "direct-tcpip": + if !s.AllowDirectTcpip { + newChannel.Reject(ssh.Prohibited, "direct-tcpip not allowed") + break + } s.handleDirectTcpip(newChannel) default: newChannel.Reject(ssh.UnknownChannelType, fmt.Sprintf("unknown channel type: %s", newChannel.ChannelType())) @@ -67,6 +78,11 @@ func (s *Server) handleSession(shell string, newChannel ssh.NewChannel) { for req := range requests { switch req.Type { case "exec": + if !s.AllowExecute { + s.Logger.Info("execution not allowed (exec)") + req.Reply(false, nil) + break + } s.handleExecRequest(req, connection) case "shell": // We only accept the default shell @@ -75,6 +91,11 @@ func (s *Server) handleSession(shell string, newChannel ssh.NewChannel) { req.Reply(true, nil) } case "pty-req": + if !s.AllowExecute { + s.Logger.Info("execution not allowed (pty-req)") + req.Reply(false, nil) + break + } termLen := req.Payload[3] w, h := parseDims(req.Payload[termLen+4:]) shf, err = s.createPty(shell, connection) @@ -140,9 +161,17 @@ func (s *Server) handleExecRequest(req *ssh.Request, connection ssh.Channel) { func (s *Server) handleSessionSubSystem(req *ssh.Request, connection ssh.Channel) { // https://github.com/pkg/sftp/blob/42e9800606febe03f9cdf1d1283719af4a5e6456/examples/go-sftp-server/main.go#L111 - ok := string(req.Payload[4:]) == "sftp" - req.Reply(ok, nil) + if string(req.Payload[4:]) != "sftp" { + req.Reply(false, nil) + return + } + if !s.AllowSftp { + s.Logger.Info("sftp not allowed") + req.Reply(false, nil) + return + } + req.Reply(true, nil) serverOptions := []sftp.ServerOption{ sftp.WithDebug(os.Stderr), } @@ -232,6 +261,11 @@ func (s *Server) HandleGlobalRequests(sshConn *ssh.ServerConn, reqs <-chan *ssh. for req := range reqs { switch req.Type { case "tcpip-forward": + if !s.AllowTcpipForward { + s.Logger.Info("tcpip-forward not allowed") + req.Reply(false, nil) + break + } s.handleTcpipForward(sshConn, req) default: // discard From ba76a4552f2cce10bd79342bf4ff0830c3867f7f Mon Sep 17 00:00:00 2001 From: Ryo Ota Date: Wed, 9 Aug 2023 20:52:35 +0900 Subject: [PATCH 09/13] docs: permissions --- README.md | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 849cc70..fbb4ec6 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,25 @@ handy-sshd -p 2222 --user "john:" --user "alice:" handy-sshd --unix-socket /tmp/my-unix-socket --user "john:" ``` +## Permissions +There are some permissions. +* --allow-direct-tcpip +* --allow-execute +* --allow-sftp +* --allow-tcpip-forward + +All permissions are allowed when nothing is specified. + +Specifying `--allow-direct-tcpip` and `--allow-execute` allows only "direct-tcpip" and command executions. +The log shows "allowed: " and "NOT allowed: " permissions as follows. + +```console +$ handy-sshd --user "john:" --allow-direct-tcpip --allow-execute +2023/08/09 20:49:35 INFO listening on :2222... +2023/08/09 20:49:35 INFO allowed: "direct-tcpip", "execute" +2023/08/09 20:49:35 INFO NOT allowed: "tcpip-forward", "sftp" +``` + ## --help ``` @@ -49,11 +68,15 @@ Usage: handy-sshd [flags] Flags: - -h, --help help for handy-sshd - --host string SSH server host (e.g. 127.0.0.1) - -p, --port uint16 SSH server port (default 2222) - --shell string Shell - --unix-socket string Unix-domain socket - --user stringArray SSH user name (e.g. "john:mypassword") - -v, --version show version + --allow-direct-tcpip client can use local forwarding and SOCKS proxy + --allow-execute client can use shell/interactive shell + --allow-sftp client can use SFTP and SSHFS + --allow-tcpip-forward client can use remote forwarding + -h, --help help for handy-sshd + --host string SSH server host (e.g. 127.0.0.1) + -p, --port uint16 SSH server port (default 2222) + --shell string Shell + --unix-socket string Unix-domain socket + --user stringArray SSH user name (e.g. "john:mypassword") + -v, --version show version ``` From 0b3b8d0f96ada67704e1da99473e8077c67c58ed Mon Sep 17 00:00:00 2001 From: Ryo Ota Date: Wed, 9 Aug 2023 20:57:13 +0900 Subject: [PATCH 10/13] deps: update --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b005b36..7f2fccc 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/pkg/sftp v1.13.5 github.com/spf13/cobra v1.7.0 golang.org/x/crypto v0.12.0 - golang.org/x/exp v0.0.0-20230807204917-050eac23e9de + golang.org/x/exp v0.0.0-20230809094429-853ea248256d ) require ( diff --git a/go.sum b/go.sum index 7fee637..4d06c32 100644 --- a/go.sum +++ b/go.sum @@ -27,8 +27,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= -golang.org/x/exp v0.0.0-20230807204917-050eac23e9de h1:l5Za6utMv/HsBWWqzt4S8X17j+kt1uVETUX5UFhn2rE= -golang.org/x/exp v0.0.0-20230807204917-050eac23e9de/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/exp v0.0.0-20230809094429-853ea248256d h1:wu5bD43Ana/nF1ZmaLr3lW/FQeJU8CcI+Ln7yWHViXE= +golang.org/x/exp v0.0.0-20230809094429-853ea248256d/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= From 54b502d3ae1994bd733fbf74bcecb535ade10d52 Mon Sep 17 00:00:00 2001 From: Ryo Ota Date: Thu, 10 Aug 2023 03:31:30 +0900 Subject: [PATCH 11/13] require one user at least --- cmd/root.go | 6 +++++- main/main.go | 2 -- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index bbd5324..9581e71 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -101,7 +101,11 @@ var RootCmd = &cobra.Command{ } sshUsers = append(sshUsers, sshUser{name: splits[0], password: splits[1]}) } - + if len(sshUsers) == 0 { + return fmt.Errorf(`No user specified +e.g. --user "john:mypassword" +e.g. --user "john:"`) + } // (base: https://gist.github.com/jpillora/b480fde82bff51a06238) sshConfig := &ssh.ServerConfig{ //Define a function to run when a client attempts a password login diff --git a/main/main.go b/main/main.go index 55e5f6c..6ac23fe 100644 --- a/main/main.go +++ b/main/main.go @@ -1,14 +1,12 @@ package main import ( - "fmt" "github.com/nwtgck/handy-sshd/cmd" "os" ) func main() { if err := cmd.RootCmd.Execute(); err != nil { - _, _ = fmt.Fprintf(os.Stderr, err.Error()) os.Exit(-1) } } From 9af7dd047f9a47c9099916b39064495d67290f0c Mon Sep 17 00:00:00 2001 From: Ryo Ota Date: Thu, 10 Aug 2023 03:35:35 +0900 Subject: [PATCH 12/13] docs: update "Permissions" --- README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index fbb4ec6..871d60c 100644 --- a/README.md +++ b/README.md @@ -41,15 +41,13 @@ handy-sshd --unix-socket /tmp/my-unix-socket --user "john:" ``` ## Permissions -There are some permissions. +**All permissions are allowed when nothing is specified.** There are some permissions. * --allow-direct-tcpip * --allow-execute * --allow-sftp * --allow-tcpip-forward -All permissions are allowed when nothing is specified. - -Specifying `--allow-direct-tcpip` and `--allow-execute` allows only "direct-tcpip" and command executions. +Specifying `--allow-direct-tcpip` and `--allow-execute` for example allows only them. The log shows "allowed: " and "NOT allowed: " permissions as follows. ```console From d41dabd21245f4d8f6fbc472ae5750aa43d52f14 Mon Sep 17 00:00:00 2001 From: Ryo Ota Date: Thu, 10 Aug 2023 03:49:30 +0900 Subject: [PATCH 13/13] bump: 0.2.0 --- CHANGELOG.md | 10 +++++++++- version/version.go | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c0a9cc..4958a1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,16 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) ## [Unreleased] +## [0.2.0] - 2023-08-09 +### Added +* Add permissions + +### Changed +* Require one user at least + ## 0.1.0 - 2023-08-08 ### Added * Initial release -[Unreleased]: https://github.com/nwtgck/handy-sshd/compare/v0.1.0...HEAD +[Unreleased]: https://github.com/nwtgck/handy-sshd/compare/v0.2.0...HEAD +[0.2.0]: https://github.com/nwtgck/handy-sshd/compare/v0.1.0...v0.2.0 diff --git a/version/version.go b/version/version.go index 31646b0..c62f7e6 100644 --- a/version/version.go +++ b/version/version.go @@ -1,3 +1,3 @@ package version -const Version = "0.1.0" +const Version = "0.2.0"