You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
frp/pkg/ssh/gateway.go

144 lines
3.6 KiB

// Copyright 2023 The frp Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package ssh
import (
"fmt"
"net"
"os"
"strconv"
"strings"
"golang.org/x/crypto/ssh"
v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/transport"
"github.com/fatedier/frp/pkg/util/log"
netpkg "github.com/fatedier/frp/pkg/util/net"
)
type Gateway struct {
bindPort int
ln net.Listener
peerServerListener *netpkg.InternalListener
sshConfig *ssh.ServerConfig
}
func NewGateway(
cfg v1.SSHTunnelGateway, bindAddr string,
peerServerListener *netpkg.InternalListener,
) (*Gateway, error) {
sshConfig := &ssh.ServerConfig{}
// privateKey
var (
privateKeyBytes []byte
err error
)
if cfg.PrivateKeyFile != "" {
privateKeyBytes, err = os.ReadFile(cfg.PrivateKeyFile)
} else {
if cfg.AutoGenPrivateKeyPath != "" {
privateKeyBytes, _ = os.ReadFile(cfg.AutoGenPrivateKeyPath)
}
if len(privateKeyBytes) == 0 {
privateKeyBytes, err = transport.NewRandomPrivateKey()
if err == nil && cfg.AutoGenPrivateKeyPath != "" {
err = os.WriteFile(cfg.AutoGenPrivateKeyPath, privateKeyBytes, 0o600)
}
}
}
if err != nil {
return nil, err
}
privateKey, err := ssh.ParsePrivateKey(privateKeyBytes)
if err != nil {
return nil, err
}
sshConfig.AddHostKey(privateKey)
sshConfig.NoClientAuth = cfg.AuthorizedKeysFile == ""
sshConfig.PublicKeyCallback = func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
authorizedKeysMap, err := loadAuthorizedKeysFromFile(cfg.AuthorizedKeysFile)
if err != nil {
log.Errorf("load authorized keys file error: %v", err)
return nil, fmt.Errorf("internal error")
}
user, ok := authorizedKeysMap[string(key.Marshal())]
if !ok {
return nil, fmt.Errorf("unknown public key for remoteAddr %q", conn.RemoteAddr())
}
return &ssh.Permissions{
Extensions: map[string]string{
"user": user,
},
}, nil
}
ln, err := net.Listen("tcp", net.JoinHostPort(bindAddr, strconv.Itoa(cfg.BindPort)))
if err != nil {
return nil, err
}
return &Gateway{
bindPort: cfg.BindPort,
ln: ln,
peerServerListener: peerServerListener,
sshConfig: sshConfig,
}, nil
}
func (g *Gateway) Run() {
for {
conn, err := g.ln.Accept()
if err != nil {
return
}
go g.handleConn(conn)
}
}
func (g *Gateway) handleConn(conn net.Conn) {
defer conn.Close()
ts, err := NewTunnelServer(conn, g.sshConfig, g.peerServerListener)
if err != nil {
return
}
if err := ts.Run(); err != nil {
log.Errorf("ssh tunnel server run error: %v", err)
}
}
func loadAuthorizedKeysFromFile(path string) (map[string]string, error) {
authorizedKeysMap := make(map[string]string) // value is username
authorizedKeysBytes, err := os.ReadFile(path)
if err != nil {
return nil, err
}
for len(authorizedKeysBytes) > 0 {
pubKey, comment, _, rest, err := ssh.ParseAuthorizedKey(authorizedKeysBytes)
if err != nil {
return nil, err
}
authorizedKeysMap[string(pubKey.Marshal())] = strings.TrimSpace(comment)
authorizedKeysBytes = rest
}
return authorizedKeysMap, nil
}