v0.0.1 ready
continuous-integration/drone/tag Build is failing
Details
continuous-integration/drone/tag Build is failing
Details
parent
42bbcc5581
commit
e1d84a9070
|
@ -0,0 +1,32 @@
|
||||||
|
kind: pipeline
|
||||||
|
name: default
|
||||||
|
steps:
|
||||||
|
- name: build
|
||||||
|
image: golang:1.17.2
|
||||||
|
commands:
|
||||||
|
- git tag $DRONE_TAG
|
||||||
|
- bash release.sh
|
||||||
|
- echo -n "latest,${DRONE_TAG#v}" > .tags
|
||||||
|
- name: gitea_release
|
||||||
|
image: plugins/gitea-release
|
||||||
|
settings:
|
||||||
|
api_key:
|
||||||
|
from_secret: gitea_api_key
|
||||||
|
base_url: https://kumoly.io
|
||||||
|
files: dist/*
|
||||||
|
checksum:
|
||||||
|
- sha256
|
||||||
|
- name: docker
|
||||||
|
image: plugins/docker
|
||||||
|
settings:
|
||||||
|
username:
|
||||||
|
from_secret: hub_user
|
||||||
|
password:
|
||||||
|
from_secret: hub_passwd
|
||||||
|
# auto_tag: true
|
||||||
|
mtu: 1000
|
||||||
|
# purge: true
|
||||||
|
repo: hub.kumoly.io/tools/configui
|
||||||
|
registry: hub.kumoly.io
|
||||||
|
trigger:
|
||||||
|
event: tag
|
|
@ -0,0 +1,17 @@
|
||||||
|
FROM golang:1.17.2-alpine3.14 as builder
|
||||||
|
RUN apk update && apk add --no-cache git tzdata
|
||||||
|
WORKDIR /src
|
||||||
|
|
||||||
|
COPY go.mod go.sum /src/
|
||||||
|
RUN go mod download
|
||||||
|
COPY . .
|
||||||
|
RUN VERSION=$(git describe --tags --abbrev=0) BUILD=$(git rev-parse --short HEAD) && \
|
||||||
|
GOOS=linux GOARCH=amd64 \
|
||||||
|
go build -ldflags "-X main.Version=${VERSION} -X main.Build=${BUILD} -w" \
|
||||||
|
-o /go/bin/breacher
|
||||||
|
|
||||||
|
FROM alpine:3.14
|
||||||
|
ENV PATH="/go/bin:${PATH}"
|
||||||
|
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
|
||||||
|
COPY --from=builder /go/bin/breacher /go/bin/breacher
|
||||||
|
ENTRYPOINT ["/go/bin/breacher"]
|
207
breacher/ssh.go
207
breacher/ssh.go
|
@ -1,12 +1,18 @@
|
||||||
package breacher
|
package breacher
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
"golang.org/x/crypto/ssh/agent"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -14,14 +20,19 @@ var (
|
||||||
sKey string
|
sKey string
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
sshCmd.Flags().StringVarP(&sPasswd, "password", "p", "", "login using password")
|
||||||
|
sshCmd.Flags().StringVarP(&sKey, "keyfile", "i", "", "login keyfile (path/to/file)")
|
||||||
|
}
|
||||||
|
|
||||||
var sshCmd = &cobra.Command{
|
var sshCmd = &cobra.Command{
|
||||||
Use: "tunnel [from address] [to address] [user@host:port]",
|
Use: "tunnel [from address] [to address] [user@host:port]",
|
||||||
Short: "ssh tunneling to access remote services",
|
Short: "ssh tunneling to access remote services",
|
||||||
Long: `ssh tunneling to access remote services
|
Long: `ssh tunneling to access remote services
|
||||||
ex.
|
ex.
|
||||||
breacher forward :8080 kumoly.io:5080
|
breacher tunnel :8080 host:80 user@example.com -p paswd
|
||||||
breacher forward :8080 :8000
|
breacher tunnel :8080 :80 user@example.com -i ~/.ssh/id_rsa
|
||||||
breacher forward --udp :8080 192.168.51.211:53
|
breacher tunnel :8080 kumoly.io:443 user@example.com
|
||||||
`,
|
`,
|
||||||
Args: cobra.ExactArgs(3),
|
Args: cobra.ExactArgs(3),
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
@ -42,12 +53,11 @@ breacher forward --udp :8080 192.168.51.211:53
|
||||||
log.Fatalln(err)
|
log.Fatalln(err)
|
||||||
}
|
}
|
||||||
if localHost == "" {
|
if localHost == "" {
|
||||||
localHost = "localhost"
|
localHost = "0.0.0.0"
|
||||||
}
|
}
|
||||||
if remoteHost == "" {
|
if remoteHost == "" {
|
||||||
remoteHost = "localhost"
|
remoteHost = "localhost"
|
||||||
}
|
}
|
||||||
st := NewSSHTunnel(localHost, localPort, remoteHost, remotePort)
|
|
||||||
split := strings.Split(args[2], "@")
|
split := strings.Split(args[2], "@")
|
||||||
if len(split) != 2 {
|
if len(split) != 2 {
|
||||||
log.Fatalln("ssh host name not valid")
|
log.Fatalln("ssh host name not valid")
|
||||||
|
@ -73,22 +83,179 @@ breacher forward --udp :8080 192.168.51.211:53
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var auth ssh.AuthMethod
|
||||||
st.server.Host = sshHost
|
if sPasswd != "" {
|
||||||
st.SetPort(sshPort)
|
auth = ssh.Password(sPasswd)
|
||||||
st.SetUser(usr)
|
} else if sKey != "" {
|
||||||
st.SetPassword("ubuntu")
|
auth = PrivateKeyFile(sKey)
|
||||||
st.SetDebug(true)
|
} else {
|
||||||
st.SetConnState(func(tun *SSHTun, state ConnState) {
|
auth = SSHAgent()
|
||||||
switch state {
|
|
||||||
case StateStarting:
|
|
||||||
log.Printf("STATE is Starting")
|
|
||||||
case StateStarted:
|
|
||||||
log.Printf("STATE is Started")
|
|
||||||
case StateStopped:
|
|
||||||
log.Printf("STATE is Stopped")
|
|
||||||
}
|
}
|
||||||
})
|
st := NewSSHTunnel(
|
||||||
|
&Endpoint{localHost, localPort, ""},
|
||||||
|
&Endpoint{remoteHost, remotePort, ""},
|
||||||
|
&Endpoint{sshHost, sshPort, usr},
|
||||||
|
auth,
|
||||||
|
)
|
||||||
|
|
||||||
log.Fatalln(st.Start())
|
log.Fatalln(st.Start())
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Endpoint struct {
|
||||||
|
Host string
|
||||||
|
Port int
|
||||||
|
User string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (endpoint *Endpoint) String() string {
|
||||||
|
return fmt.Sprintf("%s:%d", endpoint.Host, endpoint.Port)
|
||||||
|
}
|
||||||
|
|
||||||
|
type SSHTunnel struct {
|
||||||
|
Local *Endpoint
|
||||||
|
Server *Endpoint
|
||||||
|
Remote *Endpoint
|
||||||
|
Config *ssh.ClientConfig
|
||||||
|
Conns []net.Conn
|
||||||
|
SvrConns []*ssh.Client
|
||||||
|
isOpen bool
|
||||||
|
close chan interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newConnectionWaiter(listener net.Listener, c chan net.Conn) {
|
||||||
|
conn, err := listener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c <- conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tunnel *SSHTunnel) Start() error {
|
||||||
|
listener, err := net.Listen("tcp", tunnel.Local.String())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tunnel.isOpen = true
|
||||||
|
tunnel.Local.Port = listener.Addr().(*net.TCPAddr).Port
|
||||||
|
|
||||||
|
for {
|
||||||
|
if !tunnel.isOpen {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
c := make(chan net.Conn)
|
||||||
|
go newConnectionWaiter(listener, c)
|
||||||
|
log.Println("listening for new connections...")
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-tunnel.close:
|
||||||
|
log.Println("close signal received, closing...")
|
||||||
|
tunnel.isOpen = false
|
||||||
|
case conn := <-c:
|
||||||
|
tunnel.Conns = append(tunnel.Conns, conn)
|
||||||
|
log.Println("accepted connection")
|
||||||
|
go tunnel.forward(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var total int
|
||||||
|
total = len(tunnel.Conns)
|
||||||
|
for i, conn := range tunnel.Conns {
|
||||||
|
log.Printf("closing the netConn (%d of %d)\n", i+1, total)
|
||||||
|
err := conn.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
total = len(tunnel.SvrConns)
|
||||||
|
for i, conn := range tunnel.SvrConns {
|
||||||
|
log.Printf("closing the serverConn (%d of %d)\n", i+1, total)
|
||||||
|
err := conn.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = listener.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Println("tunnel closed")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tunnel *SSHTunnel) forward(localConn net.Conn) {
|
||||||
|
serverConn, err := ssh.Dial("tcp", tunnel.Server.String(), tunnel.Config)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("server dial error: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("connected to %s (1 of 2)\n", tunnel.Server.String())
|
||||||
|
tunnel.SvrConns = append(tunnel.SvrConns, serverConn)
|
||||||
|
|
||||||
|
remoteConn, err := serverConn.Dial("tcp", tunnel.Remote.String())
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("remote dial error: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tunnel.Conns = append(tunnel.Conns, remoteConn)
|
||||||
|
log.Printf("connected to %s (2 of 2)\n", tunnel.Remote.String())
|
||||||
|
copyConn := func(writer, reader net.Conn) {
|
||||||
|
_, err := io.Copy(writer, reader)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("io.Copy error: %s\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
go copyConn(localConn, remoteConn)
|
||||||
|
go copyConn(remoteConn, localConn)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tunnel *SSHTunnel) Close() {
|
||||||
|
tunnel.close <- struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSSHTunnel creates a new single-use tunnel. Supplying "0" for localport will use a random port.
|
||||||
|
func NewSSHTunnel(from, to, server *Endpoint, auth ssh.AuthMethod) *SSHTunnel {
|
||||||
|
if server.Port == 0 {
|
||||||
|
server.Port = 22
|
||||||
|
}
|
||||||
|
|
||||||
|
sshTunnel := &SSHTunnel{
|
||||||
|
Config: &ssh.ClientConfig{
|
||||||
|
User: server.User,
|
||||||
|
Auth: []ssh.AuthMethod{auth},
|
||||||
|
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
|
||||||
|
// Always accept key.
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Local: from,
|
||||||
|
Server: server,
|
||||||
|
Remote: to,
|
||||||
|
close: make(chan interface{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
return sshTunnel
|
||||||
|
}
|
||||||
|
|
||||||
|
func PrivateKeyFile(file string) ssh.AuthMethod {
|
||||||
|
buffer, err := ioutil.ReadFile(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
key, err := ssh.ParsePrivateKey(buffer)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return ssh.PublicKeys(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SSHAgent() ssh.AuthMethod {
|
||||||
|
if sshAgent, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil {
|
||||||
|
return ssh.PublicKeysCallback(agent.NewClient(sshAgent).Signers)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -1,499 +0,0 @@
|
||||||
package breacher
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"io/ioutil"
|
|
||||||
"os/user"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/ssh"
|
|
||||||
"golang.org/x/crypto/ssh/agent"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Endpoint struct {
|
|
||||||
Host string
|
|
||||||
Port int
|
|
||||||
UnixSocket string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Endpoint) connectionString() string {
|
|
||||||
if e.UnixSocket != "" {
|
|
||||||
return e.UnixSocket
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%s:%d", e.Host, e.Port)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Endpoint) connectionType() string {
|
|
||||||
if e.UnixSocket != "" {
|
|
||||||
return "unix"
|
|
||||||
}
|
|
||||||
return "tcp"
|
|
||||||
}
|
|
||||||
|
|
||||||
// AuthType is the type of authentication to use for SSH.
|
|
||||||
type AuthType int
|
|
||||||
|
|
||||||
const (
|
|
||||||
// AuthTypeKeyFile uses the keys from a SSH key file read from the system.
|
|
||||||
AuthTypeKeyFile AuthType = iota
|
|
||||||
// AuthTypeEncryptedKeyFile uses the keys from an encrypted SSH key file read from the system.
|
|
||||||
AuthTypeEncryptedKeyFile
|
|
||||||
// AuthTypeKeyReader uses the keys from a SSH key reader.
|
|
||||||
AuthTypeKeyReader
|
|
||||||
// AuthTypeEncryptedKeyReader uses the keys from an encrypted SSH key reader.
|
|
||||||
AuthTypeEncryptedKeyReader
|
|
||||||
// AuthTypePassword uses a password directly.
|
|
||||||
AuthTypePassword
|
|
||||||
// AuthTypeSSHAgent will use registered users in the ssh-agent.
|
|
||||||
AuthTypeSSHAgent
|
|
||||||
// AuthTypeAuto tries to get the authentication method automatically. See SSHTun.Start for details on
|
|
||||||
// this.
|
|
||||||
AuthTypeAuto
|
|
||||||
)
|
|
||||||
|
|
||||||
// SSHTun represents a SSH tunnel
|
|
||||||
type SSHTun struct {
|
|
||||||
*sync.Mutex
|
|
||||||
ctx context.Context
|
|
||||||
cancel context.CancelFunc
|
|
||||||
errCh chan error
|
|
||||||
user string
|
|
||||||
authType AuthType
|
|
||||||
authKeyFile string
|
|
||||||
authKeyReader io.Reader
|
|
||||||
authPassword string
|
|
||||||
server Endpoint
|
|
||||||
local Endpoint
|
|
||||||
remote Endpoint
|
|
||||||
started bool
|
|
||||||
timeout time.Duration
|
|
||||||
debug bool
|
|
||||||
connState func(*SSHTun, ConnState)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConnState represents the state of the SSH tunnel. It's returned to an optional function provided to SetConnState.
|
|
||||||
type ConnState int
|
|
||||||
|
|
||||||
const (
|
|
||||||
// StateStopped represents a stopped tunnel. A call to Start will make the state to transition to StateStarting.
|
|
||||||
StateStopped ConnState = iota
|
|
||||||
|
|
||||||
// StateStarting represents a tunnel initializing and preparing to listen for connections.
|
|
||||||
// A successful initialization will make the state to transition to StateStarted, otherwise it will transition to StateStopped.
|
|
||||||
StateStarting
|
|
||||||
|
|
||||||
// StateStarted represents a tunnel ready to accept connections.
|
|
||||||
// A call to stop or an error will make the state to transition to StateStopped.
|
|
||||||
StateStarted
|
|
||||||
)
|
|
||||||
|
|
||||||
// New creates a new SSH tunnel to the specified server redirecting a port on local localhost to a port on remote localhost.
|
|
||||||
// By default the SSH connection is made to port 22 as root and using automatic detection of the authentication
|
|
||||||
// method (see Start for details on this).
|
|
||||||
// Calling SetPassword will change the authentication to password based.
|
|
||||||
// Calling SetKeyFile will change the authentication to keyfile based with an optional key file.
|
|
||||||
// The SSH user and port can be changed with SetUser and SetPort.
|
|
||||||
// The local and remote hosts can be changed to something different than localhost with SetLocalHost and SetRemoteHost.
|
|
||||||
// The states of the tunnel can be received throgh a callback function with SetConnState.
|
|
||||||
func NewSSHTunnel(localHost string, localPort int, remoteHost string, remotePort int) *SSHTun {
|
|
||||||
return &SSHTun{
|
|
||||||
Mutex: &sync.Mutex{},
|
|
||||||
server: Endpoint{
|
|
||||||
Host: "",
|
|
||||||
Port: 22,
|
|
||||||
},
|
|
||||||
user: "root",
|
|
||||||
authType: AuthTypeAuto,
|
|
||||||
authKeyFile: "",
|
|
||||||
authPassword: "",
|
|
||||||
local: Endpoint{
|
|
||||||
Host: localHost,
|
|
||||||
Port: localPort,
|
|
||||||
},
|
|
||||||
remote: Endpoint{
|
|
||||||
Host: remoteHost,
|
|
||||||
Port: remotePort,
|
|
||||||
},
|
|
||||||
started: false,
|
|
||||||
timeout: time.Second * 15,
|
|
||||||
debug: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewUnix(localUnixSocket string, server string, remoteUnixSocket string) *SSHTun {
|
|
||||||
return &SSHTun{
|
|
||||||
Mutex: &sync.Mutex{},
|
|
||||||
server: Endpoint{
|
|
||||||
Host: server,
|
|
||||||
Port: 22,
|
|
||||||
},
|
|
||||||
user: "root",
|
|
||||||
authType: AuthTypeAuto,
|
|
||||||
authKeyFile: "",
|
|
||||||
authPassword: "",
|
|
||||||
local: Endpoint{
|
|
||||||
UnixSocket: localUnixSocket,
|
|
||||||
},
|
|
||||||
remote: Endpoint{
|
|
||||||
UnixSocket: remoteUnixSocket,
|
|
||||||
},
|
|
||||||
started: false,
|
|
||||||
timeout: time.Second * 15,
|
|
||||||
debug: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetPort changes the port where the SSH connection will be made.
|
|
||||||
func (tun *SSHTun) SetPort(port int) {
|
|
||||||
tun.server.Port = port
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetUser changes the user used to make the SSH connection.
|
|
||||||
func (tun *SSHTun) SetUser(user string) {
|
|
||||||
tun.user = user
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetKeyFile changes the authentication to key-based and uses the specified file.
|
|
||||||
// Leaving it empty defaults to the default linux private key location ($HOME/.ssh/id_rsa).
|
|
||||||
func (tun *SSHTun) SetKeyFile(file string) {
|
|
||||||
tun.authType = AuthTypeKeyFile
|
|
||||||
tun.authKeyFile = file
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetEncryptedKeyFile changes the authentication to encrypted key-based and uses the specified file and password.
|
|
||||||
// Leaving it empty defaults to the default linux private key location ($HOME/.ssh/id_rsa).
|
|
||||||
func (tun *SSHTun) SetEncryptedKeyFile(file string, password string) {
|
|
||||||
tun.authType = AuthTypeEncryptedKeyFile
|
|
||||||
tun.authKeyFile = file
|
|
||||||
tun.authPassword = password
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetKeyReader changes the authentication to key-based and uses the specified reader.
|
|
||||||
// Leaving it empty defaults to the default linux private key location ($HOME/.ssh/id_rsa).
|
|
||||||
func (tun *SSHTun) SetKeyReader(reader io.Reader) {
|
|
||||||
tun.authType = AuthTypeKeyReader
|
|
||||||
tun.authKeyReader = reader
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetEncryptedKeyReader changes the authentication to encrypted key-based and uses the specified reader and password.
|
|
||||||
// Leaving it empty defaults to the default linux private key location ($HOME/.ssh/id_rsa).
|
|
||||||
func (tun *SSHTun) SetEncryptedKeyReader(reader io.Reader, password string) {
|
|
||||||
tun.authType = AuthTypeEncryptedKeyReader
|
|
||||||
tun.authKeyReader = reader
|
|
||||||
tun.authPassword = password
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetSSHAgent changes the authentication to ssh-agent.
|
|
||||||
func (tun *SSHTun) SetSSHAgent() {
|
|
||||||
tun.authType = AuthTypeSSHAgent
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetPassword changes the authentication to password-based and uses the specified password.
|
|
||||||
func (tun *SSHTun) SetPassword(password string) {
|
|
||||||
tun.authType = AuthTypePassword
|
|
||||||
tun.authPassword = password
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetLocalHost sets the local host to redirect (defaults to localhost)
|
|
||||||
func (tun *SSHTun) SetLocalHost(host string) {
|
|
||||||
tun.local.Host = host
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetRemoteHost sets the remote host to redirect (defaults to localhost)
|
|
||||||
func (tun *SSHTun) SetRemoteHost(host string) {
|
|
||||||
tun.remote.Host = host
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetTimeout sets the connection timeouts (defaults to 15 seconds).
|
|
||||||
func (tun *SSHTun) SetTimeout(timeout time.Duration) {
|
|
||||||
tun.timeout = timeout
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetDebug enables or disables log messages (disabled by default).
|
|
||||||
func (tun *SSHTun) SetDebug(debug bool) {
|
|
||||||
tun.debug = debug
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetConnState specifies an optional callback function that is called when a SSH tunnel changes state.
|
|
||||||
// See the ConnState type and associated constants for details.
|
|
||||||
func (tun *SSHTun) SetConnState(connStateFun func(*SSHTun, ConnState)) {
|
|
||||||
tun.connState = connStateFun
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start starts the SSH tunnel. After this call, all Set* methods will have no effect until Close is called.
|
|
||||||
// Note on SSH authentication: in case the tunnel's authType is set to AuthTypeAuto the following will happen:
|
|
||||||
// The default key file will be used, if that doesn't succeed it will try to use the SSH agent.
|
|
||||||
// If that fails the whole authentication fails.
|
|
||||||
// That means if you want to use password or encrypted key file authentication, you have to specify that explicitly.
|
|
||||||
func (tun *SSHTun) Start() error {
|
|
||||||
tun.Lock()
|
|
||||||
|
|
||||||
if tun.connState != nil {
|
|
||||||
tun.connState(tun, StateStarting)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SSH config
|
|
||||||
config, err := tun.initSSHConfig()
|
|
||||||
if err != nil {
|
|
||||||
return tun.errNotStarted(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
local := tun.local.connectionString()
|
|
||||||
// Local listener
|
|
||||||
localList, err := net.Listen(tun.local.connectionType(), local)
|
|
||||||
if err != nil {
|
|
||||||
return tun.errNotStarted(fmt.Errorf("local listen on %s failed: %s", local, err.Error()))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Context and error channel
|
|
||||||
tun.ctx, tun.cancel = context.WithCancel(context.Background())
|
|
||||||
tun.errCh = make(chan error)
|
|
||||||
|
|
||||||
// Accept connections
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
localConn, err := localList.Accept()
|
|
||||||
if err != nil {
|
|
||||||
tun.errStarted(fmt.Errorf("local accept on %s failed: %s", local, err.Error()))
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if tun.debug {
|
|
||||||
log.Printf("Accepted connection from %s", localConn.RemoteAddr().String())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Launch the forward
|
|
||||||
go tun.forward(localConn, config)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Wait until someone cancels the context and stop accepting connections
|
|
||||||
go func() {
|
|
||||||
<-tun.ctx.Done()
|
|
||||||
localList.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Now others can call Stop or fail
|
|
||||||
if tun.debug {
|
|
||||||
log.Printf("Listening on %s", local)
|
|
||||||
}
|
|
||||||
tun.started = true
|
|
||||||
if tun.connState != nil {
|
|
||||||
tun.connState(tun, StateStarted)
|
|
||||||
}
|
|
||||||
tun.Unlock()
|
|
||||||
|
|
||||||
// Wait to exit
|
|
||||||
errFromCh := <-tun.errCh
|
|
||||||
return errFromCh
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tun *SSHTun) errNotStarted(err error) error {
|
|
||||||
tun.started = false
|
|
||||||
if tun.connState != nil {
|
|
||||||
tun.connState(tun, StateStopped)
|
|
||||||
}
|
|
||||||
tun.Unlock()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tun *SSHTun) errStarted(err error) {
|
|
||||||
tun.Lock()
|
|
||||||
if tun.started {
|
|
||||||
tun.cancel()
|
|
||||||
if tun.connState != nil {
|
|
||||||
tun.connState(tun, StateStopped)
|
|
||||||
}
|
|
||||||
tun.started = false
|
|
||||||
tun.errCh <- err
|
|
||||||
}
|
|
||||||
tun.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tun *SSHTun) initSSHConfig() (*ssh.ClientConfig, error) {
|
|
||||||
config := &ssh.ClientConfig{
|
|
||||||
User: tun.user,
|
|
||||||
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
Timeout: tun.timeout,
|
|
||||||
}
|
|
||||||
|
|
||||||
authMethod, err := tun.getSSHAuthMethod()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
config.Auth = []ssh.AuthMethod{authMethod}
|
|
||||||
|
|
||||||
return config, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tun *SSHTun) getSSHAuthMethod() (ssh.AuthMethod, error) {
|
|
||||||
switch tun.authType {
|
|
||||||
case AuthTypeKeyFile:
|
|
||||||
return tun.getSSHAuthMethodForKeyFile(false)
|
|
||||||
case AuthTypeEncryptedKeyFile:
|
|
||||||
return tun.getSSHAuthMethodForKeyFile(true)
|
|
||||||
case AuthTypeKeyReader:
|
|
||||||
return tun.getSSHAuthMethodForKeyReader(false)
|
|
||||||
case AuthTypeEncryptedKeyReader:
|
|
||||||
return tun.getSSHAuthMethodForKeyReader(true)
|
|
||||||
case AuthTypePassword:
|
|
||||||
return ssh.Password(tun.authPassword), nil
|
|
||||||
case AuthTypeSSHAgent:
|
|
||||||
return tun.getSSHAuthMethodForSSHAgent()
|
|
||||||
case AuthTypeAuto:
|
|
||||||
method, err := tun.getSSHAuthMethodForKeyFile(false)
|
|
||||||
if err != nil {
|
|
||||||
return tun.getSSHAuthMethodForSSHAgent()
|
|
||||||
}
|
|
||||||
return method, nil
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("unknown auth type: %d", tun.authType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tun *SSHTun) getSSHAuthMethodForKeyFile(encrypted bool) (ssh.AuthMethod, error) {
|
|
||||||
if tun.authKeyFile == "" {
|
|
||||||
usr, _ := user.Current()
|
|
||||||
if usr != nil {
|
|
||||||
tun.authKeyFile = usr.HomeDir + "/.ssh/id_rsa"
|
|
||||||
} else {
|
|
||||||
tun.authKeyFile = "/root/.ssh/id_rsa"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buf, err := ioutil.ReadFile(tun.authKeyFile)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error reading SSH key file %s: %s", tun.authKeyFile, err.Error())
|
|
||||||
}
|
|
||||||
key, err := tun.parsePrivateKey(buf, encrypted)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error reading SSH key file %s: %s", tun.authKeyFile, err.Error())
|
|
||||||
}
|
|
||||||
return key, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tun *SSHTun) getSSHAuthMethodForKeyReader(encrypted bool) (ssh.AuthMethod, error) {
|
|
||||||
buf, err := ioutil.ReadAll(tun.authKeyReader)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error reading from SSH key reader: %s", err.Error())
|
|
||||||
}
|
|
||||||
key, err := tun.parsePrivateKey(buf, encrypted)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error reading from SSH key reader: %s", err.Error())
|
|
||||||
}
|
|
||||||
return key, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tun *SSHTun) parsePrivateKey(buf []byte, encrypted bool) (ssh.AuthMethod, error) {
|
|
||||||
var key ssh.Signer
|
|
||||||
var err error
|
|
||||||
if encrypted {
|
|
||||||
key, err = ssh.ParsePrivateKeyWithPassphrase(buf, []byte(tun.authPassword))
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error parsing encrypted key: %s", err.Error())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
key, err = ssh.ParsePrivateKey(buf)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error parsing key: %s", err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ssh.PublicKeys(key), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tun *SSHTun) getSSHAuthMethodForSSHAgent() (ssh.AuthMethod, error) {
|
|
||||||
conn, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK"))
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error opening unix socket: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
agentClient := agent.NewClient(conn)
|
|
||||||
|
|
||||||
signers, err := agentClient.Signers()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error getting ssh-agent signers: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(signers) == 0 {
|
|
||||||
return nil, fmt.Errorf("no signers from ssh-agent. Use 'ssh-add' to add keys to agent")
|
|
||||||
}
|
|
||||||
|
|
||||||
return ssh.PublicKeys(signers...), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tun *SSHTun) forward(localConn net.Conn, config *ssh.ClientConfig) {
|
|
||||||
defer localConn.Close()
|
|
||||||
|
|
||||||
local := tun.local.connectionString()
|
|
||||||
server := tun.server.connectionString()
|
|
||||||
remote := tun.remote.connectionString()
|
|
||||||
|
|
||||||
sshConn, err := ssh.Dial(tun.server.connectionType(), server, config)
|
|
||||||
if err != nil {
|
|
||||||
tun.errStarted(fmt.Errorf("SSH connection to %s failed: %s", server, err.Error()))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer sshConn.Close()
|
|
||||||
if tun.debug {
|
|
||||||
log.Printf("SSH connection to %s done", server)
|
|
||||||
}
|
|
||||||
|
|
||||||
remoteConn, err := sshConn.Dial(tun.remote.connectionType(), remote)
|
|
||||||
if err != nil {
|
|
||||||
if tun.debug {
|
|
||||||
log.Printf("Remote dial to %s failed: %s", remote, err.Error())
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer remoteConn.Close()
|
|
||||||
if tun.debug {
|
|
||||||
log.Printf("Remote connection to %s done", remote)
|
|
||||||
}
|
|
||||||
|
|
||||||
connStr := fmt.Sprintf("%s -(tcp)> %s -(ssh)> %s -(tcp)> %s", localConn.RemoteAddr().String(), local, server, remote)
|
|
||||||
if tun.debug {
|
|
||||||
log.Printf("SSH tunnel OPEN: %s", connStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
myCtx, myCancel := context.WithCancel(tun.ctx)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
_, err = io.Copy(remoteConn, localConn)
|
|
||||||
if err != nil {
|
|
||||||
//log.Printf("Error on io.Copy remote->local on connection %s: %s", connStr, err.Error())
|
|
||||||
myCancel()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
_, err = io.Copy(localConn, remoteConn)
|
|
||||||
if err != nil {
|
|
||||||
//log.Printf("Error on io.Copy local->remote on connection %s: %s", connStr, err.Error())
|
|
||||||
myCancel()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
<-myCtx.Done()
|
|
||||||
myCancel()
|
|
||||||
if tun.debug {
|
|
||||||
log.Printf("SSH tunnel CLOSE: %s", connStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop closes the SSH tunnel and its connections.
|
|
||||||
// After this call all Set* methods will have effect and Start can be called again.
|
|
||||||
func (tun *SSHTun) Stop() {
|
|
||||||
tun.errStarted(nil)
|
|
||||||
}
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
VERSION=$(git describe --tags --abbrev=0)
|
||||||
|
if [ $? -ne 0 ]; then VERSION=$DRONE_TAG; fi
|
||||||
|
BUILD=$(git rev-parse --short HEAD)
|
||||||
|
if [ $? -ne 0 ]; then BUILD=${DRONE_COMMIT:0:7}; fi
|
||||||
|
|
||||||
|
PROJ=breacher
|
||||||
|
HUB=hub.kumoly.io
|
||||||
|
HUB_PROJECT=tools
|
||||||
|
DIST=dist
|
||||||
|
|
||||||
|
LDFLAGS="-ldflags \"-X main.Version=${VERSION} -X main.Build=${BUILD} -w\""
|
||||||
|
FAILURES=""
|
||||||
|
|
||||||
|
PLATFORMS="darwin/amd64 darwin/arm64"
|
||||||
|
PLATFORMS="$PLATFORMS windows/amd64"
|
||||||
|
PLATFORMS="$PLATFORMS linux/amd64"
|
||||||
|
|
||||||
|
|
||||||
|
for PLATFORM in $PLATFORMS; do
|
||||||
|
GOOS=${PLATFORM%/*}
|
||||||
|
GOARCH=${PLATFORM#*/}
|
||||||
|
BIN_FILENAME="${PROJ}"
|
||||||
|
if [[ "${GOOS}" == "windows" ]]; then BIN_FILENAME="${BIN_FILENAME}.exe"; fi
|
||||||
|
CMD="GOOS=${GOOS} GOARCH=${GOARCH} go build ${LDFLAGS} -o ${DIST}/${BIN_FILENAME} $@"
|
||||||
|
echo "${CMD}"
|
||||||
|
eval $CMD || FAILURES="${FAILURES} ${PLATFORM}"
|
||||||
|
sh -c "cd ${DIST} && tar -czf ${PROJ}-${VERSION}-${GOOS}-${GOARCH}.tar.gz ${BIN_FILENAME} && rm ${BIN_FILENAME}"
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ "${FAILURES}" != "" ]]; then
|
||||||
|
echo ""
|
||||||
|
echo "${SCRIPT_NAME} failed on: ${FAILURES}"
|
||||||
|
exit 1
|
||||||
|
fi
|
Loading…
Reference in New Issue