removed password based access
authorized keys can now be modified within the session. keep last set of keys when no valid keys were found and keys are changed during the session .
This commit is contained in:
parent
8d554a5ce1
commit
d109c72f66
@ -77,8 +77,7 @@ func (user UserActivityDetector) Write(p []byte) (int, error) {
|
||||
}
|
||||
|
||||
func sshServer(hostKeyFile string, shellCommand string,
|
||||
passwordHandler ssh.PasswordHandler,
|
||||
authorizedPublicKeys AuthorizedPublicKeys) *ssh.Server {
|
||||
authorizedPublicKeys *AuthorizedPublicKeys) *ssh.Server {
|
||||
ssh.Handle(func(sshSession ssh.Session) {
|
||||
workingDirectory, _ := os.Getwd()
|
||||
env := append(os.Environ(), fmt.Sprintf("agentdir=%s", workingDirectory))
|
||||
@ -106,7 +105,6 @@ func sshServer(hostKeyFile string, shellCommand string,
|
||||
log.Println("starting ssh server, waiting for debug sessions")
|
||||
|
||||
server := ssh.Server{
|
||||
PasswordHandler: passwordHandler,
|
||||
PublicKeyHandler: authorizedPublicKeys.authorize,
|
||||
SubsystemHandlers: map[string]ssh.SubsystemHandler{
|
||||
"sftp": SftpHandler,
|
||||
@ -185,9 +183,7 @@ func printHelp(msg string) {
|
||||
"Here <ID> is the unique id of the agent that allows rendez-vous with an end-user.\n" +
|
||||
"The end-user must specify the same id when connecting using ssh.\n" +
|
||||
"\n" +
|
||||
"--id: rendez-vous id. When specified an SSH authorized key must be used and password\n" +
|
||||
" based access is disabled. When not specified a random id is chosen by the agent and\n" +
|
||||
" password based access is possible. The password is configured on the converge server\n" +
|
||||
"--id: rendez-vous id, this is the id used to connect agents and clients. \n" +
|
||||
"--authorized-keys: SSH authorized keys file in openssh format. By default .authorized_keys in the\n" +
|
||||
" directory where the agent is started is used.\n" +
|
||||
"--warning-time: advance warning time before sessio ends (default '5m')\n" +
|
||||
@ -333,15 +329,20 @@ func main() {
|
||||
|
||||
// Authentiocation
|
||||
|
||||
passwordHandler, authorizedKeys := setupAuthentication(
|
||||
commChannel,
|
||||
serverInfo.UserPassword,
|
||||
authorizedKeysFile)
|
||||
authorizedKeys := NewAuthorizedPublicKeys(authorizedKeysFile)
|
||||
// initial check
|
||||
pubkeys := authorizedKeys.Parse()
|
||||
if len(pubkeys) == 0 {
|
||||
log.Printf("No public keys found in '%s', exiting", authorizedKeysFile)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
go comms.ListenForServerEvents(commChannel)
|
||||
|
||||
var service AgentService
|
||||
|
||||
service = ListenerServer(func() *ssh.Server {
|
||||
return sshServer("hostkey.pem", shell, passwordHandler, authorizedKeys)
|
||||
return sshServer("hostkey.pem", shell, authorizedKeys)
|
||||
})
|
||||
//service = ConnectionServer(netCatServer)
|
||||
//service = ConnectionServer(echoServer)
|
||||
@ -376,22 +377,6 @@ func main() {
|
||||
service.Run(listener)
|
||||
}
|
||||
|
||||
func setupAuthentication(commChannel comms.CommChannel,
|
||||
userPassword comms.UserPassword,
|
||||
authorizedKeysFile string) (func(ctx ssh.Context, password string) bool, AuthorizedPublicKeys) {
|
||||
|
||||
passwordHandler := func(ctx ssh.Context, password string) bool {
|
||||
// Replace with your own logic to validate username and password
|
||||
return ctx.User() == userPassword.Username && password == userPassword.Password
|
||||
}
|
||||
go comms.ListenForServerEvents(commChannel)
|
||||
authorizedKeys := ParseOpenSSHAuthorizedKeysFile(authorizedKeysFile)
|
||||
if len(authorizedKeys.keys) > 0 {
|
||||
log.Printf("A total of %d authorized ssh keys were found", len(authorizedKeys.keys))
|
||||
}
|
||||
return passwordHandler, authorizedKeys
|
||||
}
|
||||
|
||||
func chooseShell(shells []string) string {
|
||||
log.Printf("Shell search path is %v", shells)
|
||||
var err error
|
||||
|
@ -2,12 +2,16 @@ package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"converge/pkg/agent/session"
|
||||
"fmt"
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/gliderlabs/ssh"
|
||||
gossh "golang.org/x/crypto/ssh"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
func publicKeyHandler(ctx ssh.Context, key gossh.PublicKey, authorizedKey gossh.PublicKey) bool {
|
||||
@ -54,26 +58,115 @@ func readSshPublicKeys(fileName string) ([]ssh.PublicKey, error) {
|
||||
}
|
||||
|
||||
type AuthorizedPublicKeys struct {
|
||||
keys []ssh.PublicKey
|
||||
authorizedKeysFile string
|
||||
mutex sync.Mutex
|
||||
publicKeys []ssh.PublicKey
|
||||
}
|
||||
|
||||
func ParseOpenSSHAuthorizedKeysFile(authorizedKeysFile string) AuthorizedPublicKeys {
|
||||
if authorizedKeysFile == "" {
|
||||
return AuthorizedPublicKeys{}
|
||||
func NewAuthorizedPublicKeys(authorizedKeysFile string) *AuthorizedPublicKeys {
|
||||
pubkeys := AuthorizedPublicKeys{
|
||||
authorizedKeysFile: authorizedKeysFile,
|
||||
mutex: sync.Mutex{},
|
||||
publicKeys: nil,
|
||||
}
|
||||
keys, err := readSshPublicKeys(authorizedKeysFile)
|
||||
go pubkeys.monitorAuthorizedKeysFile(authorizedKeysFile)
|
||||
return &pubkeys
|
||||
}
|
||||
|
||||
func (pubkeys *AuthorizedPublicKeys) notifyUsers(message string) {
|
||||
session.MessageUsers(message)
|
||||
}
|
||||
|
||||
func (pubkeys *AuthorizedPublicKeys) setPubKeys(keys []ssh.PublicKey) {
|
||||
pubkeys.mutex.Lock()
|
||||
defer pubkeys.mutex.Unlock()
|
||||
pubkeys.publicKeys = keys
|
||||
}
|
||||
|
||||
func (pubkeys *AuthorizedPublicKeys) getPubKeys() []ssh.PublicKey {
|
||||
pubkeys.mutex.Lock()
|
||||
defer pubkeys.mutex.Unlock()
|
||||
return pubkeys.publicKeys
|
||||
}
|
||||
|
||||
func (pubkeys *AuthorizedPublicKeys) monitorAuthorizedKeysFile(authorizedPublicKeysFile string) {
|
||||
dir := filepath.Dir(authorizedPublicKeysFile)
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
log.Printf("Cannot watch authorized keys file '%s', user notifications about changes will not work: %v",
|
||||
authorizedPublicKeysFile, err)
|
||||
}
|
||||
defer watcher.Close()
|
||||
log.Println("XXX: monitor " + dir)
|
||||
err = watcher.Add(dir)
|
||||
if err != nil {
|
||||
log.Printf("Cannot watch hold file %s, user notifications for change in expiry time will be unavailable: %v", authorizedPublicKeysFile, err)
|
||||
}
|
||||
for {
|
||||
select {
|
||||
case event, ok := <-watcher.Events:
|
||||
if !ok {
|
||||
pubkeys.notifyUsers(
|
||||
fmt.Sprintf("Watching authorized keys file '%s' stopped", authorizedPublicKeysFile))
|
||||
return
|
||||
}
|
||||
base := filepath.Base(event.Name)
|
||||
log.Println("CHANGE " + base + " " + authorizedPublicKeysFile + " " + filepath.Base(authorizedPublicKeysFile))
|
||||
if base == filepath.Base(authorizedPublicKeysFile) {
|
||||
keys := pubkeys.Parse()
|
||||
if len(keys) == 0 {
|
||||
pubkeys.notifyUsers(
|
||||
fmt.Sprintf("Authorized keys file '%s' does not contain any valid keys, using last known configuration", authorizedPublicKeysFile))
|
||||
} else {
|
||||
pubkeys.notifyUsers(fmt.Sprintf("Updated authorized keys, now %d valid keys", len(keys)))
|
||||
pubkeys.setPubKeys(keys)
|
||||
}
|
||||
}
|
||||
|
||||
case err, ok := <-watcher.Errors:
|
||||
if ok {
|
||||
pubkeys.notifyUsers(fmt.Sprintf(
|
||||
"Watching authorized keys file '%s' stopped",
|
||||
pubkeys.authorizedKeysFile))
|
||||
}
|
||||
pubkeys.notifyUsers(fmt.Sprintf(
|
||||
"Watching authorized keys file '%s' stopped: %v",
|
||||
pubkeys.authorizedKeysFile, err))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (pubkeys *AuthorizedPublicKeys) Parse() []ssh.PublicKey {
|
||||
if pubkeys.authorizedKeysFile == "" {
|
||||
return nil
|
||||
}
|
||||
keys, err := readSshPublicKeys(pubkeys.authorizedKeysFile)
|
||||
if os.IsNotExist(err) {
|
||||
log.Printf("Authorized keys file '%s' not found.", authorizedKeysFile)
|
||||
return AuthorizedPublicKeys{}
|
||||
log.Printf("Authorized keys file '%s' not found.", pubkeys.authorizedKeysFile)
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
log.Println("Public key authentication will not work since no public keys were found.")
|
||||
}
|
||||
return AuthorizedPublicKeys{keys: keys}
|
||||
return keys
|
||||
}
|
||||
|
||||
func (key AuthorizedPublicKeys) authorize(ctx ssh.Context, userProvidedKey ssh.PublicKey) bool {
|
||||
for _, key := range key.keys {
|
||||
func (key *AuthorizedPublicKeys) authorize(ctx ssh.Context, userProvidedKey ssh.PublicKey) bool {
|
||||
//
|
||||
keys := key.Parse()
|
||||
if len(keys) == 0 {
|
||||
keys = key.getPubKeys()
|
||||
}
|
||||
if len(keys) > 0 {
|
||||
log.Printf("A total of %d authorized ssh keys were found", len(keys))
|
||||
} else {
|
||||
log.Printf("No valid public keys were found, login is impossible")
|
||||
// keep agent running, a user may still be logged in and could change the
|
||||
// authorizedk eys file
|
||||
// TODO: if no users are logged in, the agent should exit.
|
||||
}
|
||||
for _, key := range keys {
|
||||
if publicKeyHandler(ctx, userProvidedKey, key) {
|
||||
return true
|
||||
}
|
||||
|
@ -100,7 +100,6 @@ func main() {
|
||||
|
||||
userPassword := comms.UserPassword{
|
||||
Username: strconv.Itoa(rand.Int()),
|
||||
Password: strconv.Itoa(rand.Int()),
|
||||
}
|
||||
|
||||
username, ok := os.LookupEnv("CONVERGE_USERNAME")
|
||||
@ -110,14 +109,7 @@ func main() {
|
||||
os.Setenv("CONVERGE_USERNAME", userPassword.Username)
|
||||
}
|
||||
|
||||
password, ok := os.LookupEnv("CONVERGE_PASSWORD")
|
||||
if ok {
|
||||
userPassword.Password = password
|
||||
} else {
|
||||
os.Setenv("CONVERGE_PASSWORD", userPassword.Password)
|
||||
}
|
||||
|
||||
log.Printf("Using username '%s' and password '%s'", userPassword.Username, userPassword.Password)
|
||||
log.Printf("Using username '%s'", userPassword.Username)
|
||||
|
||||
notifications := make(chan *models.State, 10)
|
||||
admin := converge.NewAdmin(notifications)
|
||||
|
@ -9,5 +9,4 @@ services:
|
||||
- 8000:8000
|
||||
environment:
|
||||
CONVERGE_USERNAME: abc
|
||||
CONVERGE_PASSWORD: "123"
|
||||
TZ: "Japan"
|
||||
|
2
go.mod
2
go.mod
@ -1,6 +1,6 @@
|
||||
module converge
|
||||
|
||||
go 1.21
|
||||
go 1.22.5
|
||||
|
||||
require (
|
||||
github.com/ActiveState/termtest/conpty v0.5.0
|
||||
|
@ -24,8 +24,5 @@ spec:
|
||||
env:
|
||||
- name: CONVERGE_USERNAME
|
||||
value: converge
|
||||
- name: CONVERGE_PASSWORD
|
||||
# change this password in your final deployment
|
||||
value: "abc123"
|
||||
|
||||
|
||||
|
||||
|
@ -137,6 +137,12 @@ func UserActivityDetected() {
|
||||
}
|
||||
}
|
||||
|
||||
func MessageUsers(message string) {
|
||||
events <- func() {
|
||||
messageUsers(message)
|
||||
}
|
||||
}
|
||||
|
||||
// Internal interface synchronous
|
||||
|
||||
func userActivityDetected() {
|
||||
@ -151,12 +157,12 @@ func userActivityDetected() {
|
||||
func monitorHoldFile() {
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
log.Printf("Cannot watch old file %s, user notifications for change in expiry time will be unavailable: %v", holdFilename, err)
|
||||
log.Printf("Cannot watch hold file %s, user notifications for change in expiry time will be unavailable: %v", holdFilename, err)
|
||||
}
|
||||
defer watcher.Close()
|
||||
err = watcher.Add(".")
|
||||
if err != nil {
|
||||
log.Printf("Cannot watch old file %s, user notifications for change in expiry time will be unavailable: %v", holdFilename, err)
|
||||
log.Printf("Cannot watch hold file %s, user notifications for change in expiry time will be unavailable: %v", holdFilename, err)
|
||||
}
|
||||
for {
|
||||
select {
|
||||
|
@ -49,7 +49,6 @@ type ProtocolVersion struct {
|
||||
|
||||
type UserPassword struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
// initialization mesaage when agent connects to server
|
||||
|
Loading…
Reference in New Issue
Block a user