Compare commits
	
		
			7 Commits
		
	
	
		
			2817417eca
			...
			v0.0.1
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 1d292ec21a | |||
| ee828c3e24 | |||
| a9fdf0a053 | |||
| ca02d9b609 | |||
| 792dfdba78 | |||
| 4f7a8f0b04 | |||
| 6d6ed30227 | 
							
								
								
									
										70
									
								
								.goreleaser.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								.goreleaser.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,70 @@ | |||||||
|  | before: | ||||||
|  |   hooks: | ||||||
|  |     - go mod tidy | ||||||
|  | builds: | ||||||
|  |   - id: seashell | ||||||
|  |     env: | ||||||
|  |       - CGO_ENABLED=0 | ||||||
|  |     binary: seashell | ||||||
|  |     goos: | ||||||
|  |       - linux | ||||||
|  |     goarch: | ||||||
|  |       - amd64 | ||||||
|  |       - "386" | ||||||
|  |       - arm64 | ||||||
|  |       - arm | ||||||
|  |       - riscv64 | ||||||
|  | archives: | ||||||
|  |   - files: | ||||||
|  |       - seashell.service | ||||||
|  | nfpms: | ||||||
|  |   - id: seashell | ||||||
|  |     description: "SSH server with virtual hosts and username-based routing" | ||||||
|  |     homepage: 'https://gitea.elara.ws/Elara6331/seashell' | ||||||
|  |     maintainer: 'Elara Ivy <elara@elara.ws>' | ||||||
|  |     license: AGPLv3 | ||||||
|  |     formats: | ||||||
|  |       - deb | ||||||
|  |       - rpm | ||||||
|  |       - apk | ||||||
|  |       - archlinux | ||||||
|  |     provides: | ||||||
|  |       - seashell | ||||||
|  |     conflicts: | ||||||
|  |       - seashell | ||||||
|  |     contents: | ||||||
|  |       - src: seashell.service | ||||||
|  |         dst: /etc/systemd/system/seashell.service | ||||||
|  | aurs: | ||||||
|  |   - name: seashell-bin | ||||||
|  |     description: "SSH server with virtual hosts and username-based routing" | ||||||
|  |     homepage: 'https://gitea.elara.ws/Elara6331/seashell' | ||||||
|  |     maintainers: | ||||||
|  |       - 'Elara Ivy <elara@elara.ws>' | ||||||
|  |     license: AGPLv3 | ||||||
|  |     private_key: '{{ .Env.AUR_KEY }}' | ||||||
|  |     git_url: 'ssh://aur@aur.archlinux.org/seashell-bin.git' | ||||||
|  |     provides: | ||||||
|  |       - seashell | ||||||
|  |     conflicts: | ||||||
|  |       - seashell | ||||||
|  |     package: |- | ||||||
|  |       # binaries | ||||||
|  |       install -Dm755 ./seashell "${pkgdir}/usr/bin/seashell" | ||||||
|  |        | ||||||
|  |       # services | ||||||
|  |       install -Dm644 ./seashell.service "${pkgdir}/etc/systemd/system/seashell.service"       | ||||||
|  | release: | ||||||
|  |   gitea: | ||||||
|  |     owner: Elara6331 | ||||||
|  |     name: seashell | ||||||
|  | gitea_urls: | ||||||
|  |   api: 'https://gitea.elara.ws/api/v1/' | ||||||
|  |   download: 'https://gitea.elara.ws' | ||||||
|  |   skip_tls_verify: false | ||||||
|  | checksum: | ||||||
|  |   name_template: 'checksums.txt' | ||||||
|  | snapshot: | ||||||
|  |   name_template: "{{ incpatch .Version }}-next" | ||||||
|  | changelog: | ||||||
|  |   sort: asc | ||||||
							
								
								
									
										25
									
								
								.woodpecker.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								.woodpecker.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | labels: | ||||||
|  |   platform: linux/amd64 | ||||||
|  |  | ||||||
|  | steps: | ||||||
|  |   docker: | ||||||
|  |     image: gitea.elara.ws/elara6331/builder | ||||||
|  |     environment: | ||||||
|  |       - REGISTRY=gitea.elara.ws | ||||||
|  |       - REGISTRY_USERNAME=Elara6331 | ||||||
|  |       - KO_DOCKER_REPO=gitea.elara.ws/elara6331 | ||||||
|  |       - KO_DEFAULTBASEIMAGE=gitea.elara.ws/elara6331/static | ||||||
|  |     secrets: [ registry_password ] | ||||||
|  |     commands: | ||||||
|  |       - registry-login | ||||||
|  |       - ko build -B --platform=linux/amd64,linux/arm64,linux/riscv64 -t latest,${CI_COMMIT_TAG} --sbom=none | ||||||
|  |     when: | ||||||
|  |       event: tag | ||||||
|  |    | ||||||
|  |   release: | ||||||
|  |     image: goreleaser/goreleaser | ||||||
|  |     commands: | ||||||
|  |       - goreleaser release | ||||||
|  |     secrets: [ gitea_token, aur_key ] | ||||||
|  |     when: | ||||||
|  |       event: tag | ||||||
| @@ -1,5 +1,7 @@ | |||||||
| <p align="center"> | <p align="center"> | ||||||
| <img src="assets/seashell-text.svg" width="250"> | <img src="assets/seashell-text.svg" width="250" alt="Seashell logo"><br><br> | ||||||
|  | <a href="https://goreportcard.com/report/go.elara.ws/seashell"><img src="https://goreportcard.com/badge/go.elara.ws/seashell?style=for-the-badge" alt="Go Report Card"></a>  | ||||||
|  | <a href="https://gitea.elara.ws/Elara6331/seashell/wiki/Home"><img src="https://img.shields.io/badge/read%20the-docs-purple?style=for-the-badge" alt="Read the Docs"></a> | ||||||
| </p> | </p> | ||||||
|  |  | ||||||
| --- | --- | ||||||
|   | |||||||
| @@ -22,8 +22,6 @@ | |||||||
| package backends | package backends | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"strings" |  | ||||||
|  |  | ||||||
| 	"github.com/zclconf/go-cty/cty" | 	"github.com/zclconf/go-cty/cty" | ||||||
| 	"go.elara.ws/seashell/internal/config" | 	"go.elara.ws/seashell/internal/config" | ||||||
| 	"go.elara.ws/seashell/internal/router" | 	"go.elara.ws/seashell/internal/router" | ||||||
| @@ -84,17 +82,6 @@ func ctyObjToStringMap(o *cty.Value) map[string]string { | |||||||
| 	return out | 	return out | ||||||
| } | } | ||||||
|  |  | ||||||
| // sshGetenv gets an environment variable from the SSH session |  | ||||||
| func sshGetenv(env []string, key string) string { |  | ||||||
| 	for _, kv := range env { |  | ||||||
| 		before, after, ok := strings.Cut(kv, "=") |  | ||||||
| 		if ok && before == key { |  | ||||||
| 			return after |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return "" |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // valueOr returns the value that v points to | // valueOr returns the value that v points to | ||||||
| // or a default value if v is nil. | // or a default value if v is nil. | ||||||
| func valueOr[T any](v *T, or T) T { | func valueOr[T any](v *T, or T) T { | ||||||
|   | |||||||
| @@ -41,6 +41,7 @@ type dockerSettings struct { | |||||||
| 	Command    *cty.Value `cty:"command"` | 	Command    *cty.Value `cty:"command"` | ||||||
| 	Privileged *bool      `cty:"privileged"` | 	Privileged *bool      `cty:"privileged"` | ||||||
| 	User       *string    `cty:"user"` | 	User       *string    `cty:"user"` | ||||||
|  | 	UserMap    *cty.Value `cty:"user_map"` | ||||||
| } | } | ||||||
|  |  | ||||||
| // Docker is the docker backend. It returns a handler that connects | // Docker is the docker backend. It returns a handler that connects | ||||||
| @@ -62,6 +63,17 @@ func Docker(route config.Route) router.Handler { | |||||||
| 		if !ok { | 		if !ok { | ||||||
| 			return errors.New("this route only accepts pty sessions (try adding the -t flag)") | 			return errors.New("this route only accepts pty sessions (try adding the -t flag)") | ||||||
| 		} | 		} | ||||||
|  | 		 | ||||||
|  | 		if opts.User == nil { | ||||||
|  | 			userMap := ctyObjToStringMap(opts.UserMap) | ||||||
|  | 			user, _ := sshctx.GetUser(sess.Context()) | ||||||
|  |  | ||||||
|  | 			if muser, ok := userMap[user.Name]; ok { | ||||||
|  | 				opts.User = &muser | ||||||
|  | 			} else { | ||||||
|  | 				opts.User = &user.Name | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		c, err := client.NewClientWithOpts( | 		c, err := client.NewClientWithOpts( | ||||||
| 			client.WithHostFromEnv(), | 			client.WithHostFromEnv(), | ||||||
| @@ -72,11 +84,6 @@ func Docker(route config.Route) router.Handler { | |||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if opts.User == nil { |  | ||||||
| 			envUser := sshGetenv(sess.Environ(), "DOCKER_USER") |  | ||||||
| 			opts.User = &envUser |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		cmd := sess.Command() | 		cmd := sess.Command() | ||||||
| 		if len(cmd) == 0 { | 		if len(cmd) == 0 { | ||||||
| 			cmd = ctyTupleToStrings(opts.Command) | 			cmd = ctyTupleToStrings(opts.Command) | ||||||
|   | |||||||
| @@ -42,9 +42,10 @@ import ( | |||||||
| // proxySettings represents settings for the proxy backend. | // proxySettings represents settings for the proxy backend. | ||||||
| type proxySettings struct { | type proxySettings struct { | ||||||
| 	Server      string     `cty:"server"` | 	Server      string     `cty:"server"` | ||||||
|  | 	Port        *uint       `cty:"port"` | ||||||
| 	User        *string    `cty:"user"` | 	User        *string    `cty:"user"` | ||||||
| 	PrivkeyPath *string    `cty:"privkey"` | 	PrivkeyPath *string    `cty:"privkey"` | ||||||
| 	UserMap     *cty.Value `cty:"userMap"` | 	UserMap     *cty.Value `cty:"user_map"` | ||||||
| } | } | ||||||
|  |  | ||||||
| // Proxy is the proxy backend. It returns a handler that establishes a proxy | // Proxy is the proxy backend. It returns a handler that establishes a proxy | ||||||
| @@ -77,6 +78,11 @@ func Proxy(route config.Route) router.Handler { | |||||||
| 				opts.User = &user.Name | 				opts.User = &user.Name | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  | 		 | ||||||
|  | 		if opts.Port == nil { | ||||||
|  | 			port := uint(22) | ||||||
|  | 			opts.Port = &port | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		auth := goph.Auth{ | 		auth := goph.Auth{ | ||||||
| 			gossh.PasswordCallback(requestPassword(opts, sess)), | 			gossh.PasswordCallback(requestPassword(opts, sess)), | ||||||
| @@ -96,25 +102,27 @@ func Proxy(route config.Route) router.Handler { | |||||||
| 			auth = append(goph.Auth{gossh.PublicKeys(pk)}, auth...) | 			auth = append(goph.Auth{gossh.PublicKeys(pk)}, auth...) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		c, err := goph.New(*opts.User, opts.Server, auth) | 		c, err := goph.NewConn(&goph.Config{ | ||||||
|  | 			Auth: auth, | ||||||
|  | 			User: *opts.User, | ||||||
|  | 			Addr: opts.Server, | ||||||
|  | 			Port: *opts.Port, | ||||||
|  | 			Callback: func(host string, remote net.Addr, key gossh.PublicKey) error { | ||||||
|  | 				found, err := goph.CheckKnownHost(host, remote, key, "") | ||||||
|  | 				if !found { | ||||||
|  | 					if err = goph.AddKnownHost(host, remote, key, ""); err != nil { | ||||||
|  | 						return err | ||||||
|  | 					} | ||||||
|  | 				} else if err != nil { | ||||||
|  | 					return err | ||||||
|  | 				} | ||||||
|  | 				return nil | ||||||
|  | 			}, | ||||||
|  | 		}) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		knownHostHandler, err := goph.DefaultKnownHosts() |  | ||||||
| 		if err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		c.Config.Callback = func(host string, remote net.Addr, key gossh.PublicKey) error { |  | ||||||
| 			println("hi") |  | ||||||
| 			err = goph.AddKnownHost(host, remote, key, "") |  | ||||||
| 			if err != nil { |  | ||||||
| 				return err |  | ||||||
| 			} |  | ||||||
| 			return knownHostHandler(host, remote, key) |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		baseCmd := sess.Command() | 		baseCmd := sess.Command() | ||||||
|  |  | ||||||
| 		var userCmd string | 		var userCmd string | ||||||
|   | |||||||
							
								
								
									
										11
									
								
								seashell.service
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								seashell.service
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | |||||||
|  | [Unit] | ||||||
|  | Description=Seashell SSH Server | ||||||
|  | After=network.target | ||||||
|  |  | ||||||
|  | [Service] | ||||||
|  | ExecStart=seashell | ||||||
|  | Restart=always | ||||||
|  | StandardOutput=journal | ||||||
|  |  | ||||||
|  | [Install] | ||||||
|  | WantedBy=default.target | ||||||
		Reference in New Issue
	
	Block a user