2022-05-16 11:32:00 -04:00
package ssh
2022-03-22 11:03:15 -04:00
import (
"context"
"fmt"
"log"
"os"
"sync"
2022-05-16 11:32:00 -04:00
"github.com/edgelesssys/constellation/internal/deploy/user"
2022-03-22 11:03:15 -04:00
)
2022-05-16 11:32:00 -04:00
// UserKey describes an user that should be created with a corresponding public SSH key.
type UserKey struct {
2022-05-17 04:52:37 -04:00
Username string
PublicKey string
2022-05-16 11:32:00 -04:00
}
2022-03-22 11:03:15 -04:00
// SSHAccess reads ssh public keys from a channel, creates the specified users if required and writes the public keys to the users authorized_keys file.
type SSHAccess struct {
2022-05-16 11:32:00 -04:00
userManager user . LinuxUserManager
2022-03-22 11:03:15 -04:00
authorized map [ string ] bool
mux sync . Mutex
}
// NewSSHAccess creates a new SSHAccess.
2022-05-16 11:32:00 -04:00
func NewSSHAccess ( userManager user . LinuxUserManager ) * SSHAccess {
2022-03-22 11:03:15 -04:00
return & SSHAccess {
2022-05-16 11:32:00 -04:00
userManager : userManager ,
mux : sync . Mutex { } ,
authorized : map [ string ] bool { } ,
2022-03-22 11:03:15 -04:00
}
}
// alreadyAuthorized checks if key was written to authorized keys before.
2022-05-16 11:32:00 -04:00
func ( s * SSHAccess ) alreadyAuthorized ( sshKey UserKey ) bool {
_ , ok := s . authorized [ fmt . Sprintf ( "%s:%s" , sshKey . Username , sshKey . PublicKey ) ]
2022-03-22 11:03:15 -04:00
return ok
}
// rememberAuthorized marks this key as already written to authorized keys..
2022-05-16 11:32:00 -04:00
func ( s * SSHAccess ) rememberAuthorized ( sshKey UserKey ) {
s . authorized [ fmt . Sprintf ( "%s:%s" , sshKey . Username , sshKey . PublicKey ) ] = true
2022-03-22 11:03:15 -04:00
}
2022-05-16 11:32:00 -04:00
func ( s * SSHAccess ) DeploySSHAuthorizedKey ( ctx context . Context , sshKey UserKey ) error {
2022-03-22 11:03:15 -04:00
// allow only one thread to write to authorized keys, create users and update the authorized map at a time
s . mux . Lock ( )
defer s . mux . Unlock ( )
if s . alreadyAuthorized ( sshKey ) {
return nil
}
log . Printf ( "Trying to deploy ssh key for %s\n" , sshKey . Username )
user , err := s . userManager . EnsureLinuxUserExists ( ctx , sshKey . Username )
if err != nil {
return err
}
// CoreOS uses https://github.com/coreos/ssh-key-dir to search for ssh keys in ~/.ssh/authorized_keys.d/*
sshFolder := fmt . Sprintf ( "%s/.ssh" , user . Home )
authorized_keys_d := fmt . Sprintf ( "%s/authorized_keys.d" , sshFolder )
2022-05-16 11:32:00 -04:00
if err := s . userManager . Fs . MkdirAll ( authorized_keys_d , 0 o700 ) ; err != nil {
2022-03-22 11:03:15 -04:00
return err
}
2022-05-16 11:32:00 -04:00
if err := s . userManager . Fs . Chown ( sshFolder , user . Uid , user . Gid ) ; err != nil {
2022-03-22 11:03:15 -04:00
return err
}
2022-05-16 11:32:00 -04:00
if err := s . userManager . Fs . Chown ( authorized_keys_d , user . Uid , user . Gid ) ; err != nil {
2022-03-22 11:03:15 -04:00
return err
}
2022-05-16 11:32:00 -04:00
authorizedKeysPath := fmt . Sprintf ( "%s/ssh-keys" , authorized_keys_d )
authorizedKeysFile , err := s . userManager . Fs . OpenFile ( authorizedKeysPath , os . O_APPEND | os . O_CREATE | os . O_WRONLY , 0 o644 )
2022-03-22 11:03:15 -04:00
if err != nil {
return err
}
2022-05-16 11:32:00 -04:00
_ , err = authorizedKeysFile . WriteString ( fmt . Sprintf ( "%s %s\n" , sshKey . PublicKey , sshKey . Username ) )
2022-03-22 11:03:15 -04:00
if err != nil {
return err
}
if err := authorizedKeysFile . Close ( ) ; err != nil {
return err
}
2022-05-16 11:32:00 -04:00
if err := s . userManager . Fs . Chown ( authorizedKeysPath , user . Uid , user . Gid ) ; err != nil {
2022-03-22 11:03:15 -04:00
return err
}
2022-05-16 11:32:00 -04:00
if err := s . userManager . Fs . Chmod ( authorizedKeysPath , 0 o644 ) ; err != nil {
2022-03-22 11:03:15 -04:00
return err
}
s . rememberAuthorized ( sshKey )
log . Printf ( "Successfully authorized %s\n" , sshKey . Username )
return nil
}