@@ -306,9 +326,10 @@
Account Users
-
View, edit, update, and remove users that can access this gPanel Account
+
View, edit, update, and remove users that can access the gPanel Account and utilize SSH.
+
@@ -370,6 +391,8 @@
+
+
diff --git a/main.go b/gpanel.go
similarity index 100%
rename from main.go
rename to gpanel.go
diff --git a/pkg/api/bundle/create.go b/pkg/api/bundle/create.go
index 258af71..23f3f81 100644
--- a/pkg/api/bundle/create.go
+++ b/pkg/api/bundle/create.go
@@ -13,6 +13,7 @@ import (
"github.com/Ennovar/gPanel/pkg/encryption"
"github.com/Ennovar/gPanel/pkg/file"
"github.com/Ennovar/gPanel/pkg/gpaccount"
+ "github.com/Ennovar/gPanel/pkg/system"
)
func Create(res http.ResponseWriter, req *http.Request, logger *log.Logger, bundles map[string]*gpaccount.Controller) bool {
@@ -123,6 +124,13 @@ func Create(res http.ResponseWriter, req *http.Request, logger *log.Logger, bund
}
ds.Close()
+ err, err2 := system.CreateBundleUser(createBundleRequestData.Name)
+ if err != nil {
+ logger.Println(req.URL.Path + "::" + err.Error() + " AND " + err2.Error())
+ http.Error(res, err2.Error(), http.StatusInternalServerError)
+ return false
+ }
+
bundles[createBundleRequestData.Name] = gpaccount.New(newBundle+"/", databaseBundlePorts.Account, databaseBundlePorts.Public)
_ = bundles[createBundleRequestData.Name].Start()
_ = bundles[createBundleRequestData.Name].Public.Start()
diff --git a/pkg/api/ssh/addkey.go b/pkg/api/ssh/addkey.go
new file mode 100644
index 0000000..792cd9b
--- /dev/null
+++ b/pkg/api/ssh/addkey.go
@@ -0,0 +1,39 @@
+package ssh
+
+import (
+ "encoding/json"
+ "log"
+ "net/http"
+ "strconv"
+
+ "github.com/Ennovar/gPanel/pkg/system"
+)
+
+func AddKey(res http.ResponseWriter, req *http.Request, logger *log.Logger) bool {
+ if req.Method != "UPDATE" {
+ logger.Println(req.URL.Path + "::" + req.Method + "::" + strconv.Itoa(http.StatusMethodNotAllowed) + "::" + http.StatusText(http.StatusMethodNotAllowed))
+ http.Error(res, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
+ return false
+ }
+
+ var requestData struct {
+ Username string `json:"username"`
+ PublicKey string `json:"publickey"`
+ }
+
+ err := json.NewDecoder(req.Body).Decode(&requestData)
+ if err != nil {
+ logger.Println(req.URL.Path + "::" + err.Error())
+ http.Error(res, err.Error(), http.StatusBadRequest)
+ return false
+ }
+
+ if err = system.AddAuthorizedKey(requestData.Username, requestData.PublicKey); err != nil {
+ logger.Println(req.URL.Path + "::" + err.Error())
+ http.Error(res, err.Error(), http.StatusInternalServerError)
+ return false
+ }
+
+ res.WriteHeader(http.StatusNoContent)
+ return true
+}
diff --git a/pkg/api/ssh/deletekey.go b/pkg/api/ssh/deletekey.go
new file mode 100644
index 0000000..865278f
--- /dev/null
+++ b/pkg/api/ssh/deletekey.go
@@ -0,0 +1,39 @@
+package ssh
+
+import (
+ "encoding/json"
+ "log"
+ "net/http"
+ "strconv"
+
+ "github.com/Ennovar/gPanel/pkg/system"
+)
+
+func DeleteKey(res http.ResponseWriter, req *http.Request, logger *log.Logger) bool {
+ if req.Method != "UPDATE" {
+ logger.Println(req.URL.Path + "::" + req.Method + "::" + strconv.Itoa(http.StatusMethodNotAllowed) + "::" + http.StatusText(http.StatusMethodNotAllowed))
+ http.Error(res, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
+ return false
+ }
+
+ var requestData struct {
+ Username string `json:"username"`
+ PublicKey string `json:"publickey"`
+ }
+
+ err := json.NewDecoder(req.Body).Decode(&requestData)
+ if err != nil {
+ logger.Println(req.URL.Path + "::" + err.Error())
+ http.Error(res, err.Error(), http.StatusBadRequest)
+ return false
+ }
+
+ if err = system.DeleteAuthorizedKey(requestData.Username, requestData.PublicKey); err != nil {
+ logger.Println(req.URL.Path + "::" + err.Error())
+ http.Error(res, err.Error(), http.StatusInternalServerError)
+ return false
+ }
+
+ res.WriteHeader(http.StatusNoContent)
+ return true
+}
diff --git a/pkg/gpaccount/apihandler.go b/pkg/gpaccount/apihandler.go
index e59fe46..e16218c 100644
--- a/pkg/gpaccount/apihandler.go
+++ b/pkg/gpaccount/apihandler.go
@@ -5,12 +5,13 @@ import (
"net/http"
"strings"
+ "github.com/Ennovar/gPanel/pkg/api/domain"
"github.com/Ennovar/gPanel/pkg/api/ip"
logapi "github.com/Ennovar/gPanel/pkg/api/log"
"github.com/Ennovar/gPanel/pkg/api/server"
- "github.com/Ennovar/gPanel/pkg/api/user"
- "github.com/Ennovar/gPanel/pkg/api/domain"
"github.com/Ennovar/gPanel/pkg/api/settings"
+ "github.com/Ennovar/gPanel/pkg/api/ssh"
+ "github.com/Ennovar/gPanel/pkg/api/user"
)
func (con *Controller) apiHandler(res http.ResponseWriter, req *http.Request) (bool, bool) {
@@ -67,6 +68,10 @@ func (con *Controller) apiHandler(res http.ResponseWriter, req *http.Request) (b
return true, domain.Unlink(res, req, con.APILogger)
case "/settings/get_nameservers":
return true, settings.GetNameservers(res, req, con.APILogger)
+ case "/ssh/addkey":
+ return true, ssh.AddKey(res, req, con.APILogger)
+ case "/ssh/deletekey":
+ return true, ssh.DeleteKey(res, req, con.APILogger)
default:
return false, false
}
diff --git a/pkg/system/keys.go b/pkg/system/keys.go
new file mode 100644
index 0000000..15b1294
--- /dev/null
+++ b/pkg/system/keys.go
@@ -0,0 +1,49 @@
+package system
+
+import (
+ "io/ioutil"
+ "os"
+ "strings"
+)
+
+func AddAuthorizedKey(username, key string) error {
+ f, err := os.OpenFile("/home/"+username+"/.ssh/authorized_keys", os.O_APPEND|os.O_WRONLY, 0644)
+ if err != nil {
+ return err
+ }
+
+ if _, err = f.WriteString(key); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func DeleteAuthorizedKey(username, key string) error {
+ old, err := ioutil.ReadFile("/home/" + username + "/.ssh/authorized_keys")
+ if err != nil {
+ return err
+ }
+
+ lines := strings.Split(string(old), "\n")
+
+ for i, line := range lines {
+ if strings.Contains(line, key) {
+ lines = append(lines[:i], lines[i+1:]...)
+ break
+ }
+ }
+
+ new := strings.Join(lines, "\n")
+
+ f, err := os.OpenFile("/home/"+username+"/.ssh/authorized_keys", os.O_TRUNC|os.O_WRONLY, 0644)
+ if err != nil {
+ return err
+ }
+
+ if _, err = f.WriteString(new); err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/pkg/system/user.go b/pkg/system/user.go
new file mode 100644
index 0000000..9d90518
--- /dev/null
+++ b/pkg/system/user.go
@@ -0,0 +1,160 @@
+package system
+
+import (
+ "bytes"
+ "errors"
+ "os/exec"
+)
+
+func CreateBundleUser(username string) (error, error) {
+ var cerr bytes.Buffer
+ var err error
+
+ adduserArgs := []string{
+ "--disabled-password",
+ "--gecos",
+ "",
+ username,
+ }
+
+ keygenArgs := []string{
+ "-t",
+ "rsa",
+ "-N",
+ "",
+ "-f",
+ "/home/" + username + "/.ssh/id_rsa",
+ }
+
+ // Add the user
+ cmd := exec.Command("adduser", adduserArgs...)
+ cmd.Stderr = &cerr
+
+ if err = cmd.Run(); err != nil {
+ return err, errors.New(cerr.String())
+ }
+
+ // Create the .ssh folder for said user
+ cmd = exec.Command("mkdir", "/home/"+username+"/.ssh")
+ cmd.Stderr = &cerr
+
+ if err = cmd.Run(); err != nil {
+ return err, errors.New(cerr.String())
+ }
+
+ // Create authorized_keys file
+ cmd = exec.Command("touch", "/home/"+username+"/.ssh/authorized_keys")
+ cmd.Stderr = &cerr
+
+ if err = cmd.Run(); err != nil {
+ return err, errors.New(cerr.String())
+ }
+
+ // Put root public key into authorized_keys file
+ cmd = exec.Command("cp", "/root/.ssh/id_rsa.pub", "/home/"+username+"/.ssh/authorized_keys")
+ cmd.Stderr = &cerr
+
+ if err = cmd.Run(); err != nil {
+ return err, errors.New(cerr.String())
+ }
+
+ // Create the host key-pair for said user
+ cmd = exec.Command("ssh-keygen", keygenArgs...)
+ cmd.Stderr = &cerr
+
+ if err = cmd.Run(); err != nil {
+ return err, errors.New(cerr.String())
+ }
+
+ // Create document root for said user
+ cmd = exec.Command("mkdir", "/home/"+username+"/document_root")
+ cmd.Stderr = &cerr
+
+ if err = cmd.Run(); err != nil {
+ return err, errors.New(cerr.String())
+ }
+
+ /* OWNERSHIP AND FILE PERMISSIONS START */
+ cmd = exec.Command("chmod", "700", "/home/"+username+"/.ssh")
+ cmd.Stderr = &cerr
+
+ if err = cmd.Run(); err != nil {
+ return err, errors.New(cerr.String())
+ }
+
+ cmd = exec.Command("chmod", "600", "/home/"+username+"/.ssh/id_rsa")
+ cmd.Stderr = &cerr
+
+ if err = cmd.Run(); err != nil {
+ return err, errors.New(cerr.String())
+ }
+
+ cmd = exec.Command("chmod", "644", "/home/"+username+"/.ssh/id_rsa.pub")
+ cmd.Stderr = &cerr
+
+ if err = cmd.Run(); err != nil {
+ return err, errors.New(cerr.String())
+ }
+
+ cmd = exec.Command("chmod", "644", "/home/"+username+"/.ssh/authorized_keys")
+ cmd.Stderr = &cerr
+
+ if err = cmd.Run(); err != nil {
+ return err, errors.New(cerr.String())
+ }
+
+ cmd = exec.Command("chown", username+":", "/home/"+username+"/.ssh")
+ cmd.Stderr = &cerr
+
+ if err = cmd.Run(); err != nil {
+ return err, errors.New(cerr.String())
+ }
+
+ cmd = exec.Command("chown", username+":", "/home/"+username+"/.ssh/id_rsa")
+ cmd.Stderr = &cerr
+
+ if err = cmd.Run(); err != nil {
+ return err, errors.New(cerr.String())
+ }
+
+ cmd = exec.Command("chown", username+":", "/home/"+username+"/.ssh/id_rsa.pub")
+ cmd.Stderr = &cerr
+
+ if err = cmd.Run(); err != nil {
+ return err, errors.New(cerr.String())
+ }
+
+ cmd = exec.Command("chown", username+":", "/home/"+username+"/.ssh/authorized_keys")
+ cmd.Stderr = &cerr
+
+ if err = cmd.Run(); err != nil {
+ return err, errors.New(cerr.String())
+ }
+ /* OWNERSHIP AND FILE PERMISSIONS END */
+
+ return nil, nil
+}
+
+func DeleteBundleUser(username string) (error, error) {
+ var cerr bytes.Buffer
+ var err error
+
+ // Delete the user and try to remove all files associated
+ cmd := exec.Command("deluser", "--quiet", username)
+ cmd.Stderr = &cerr
+
+ if err = cmd.Run(); err != nil {
+ return err, errors.New(cerr.String())
+ }
+
+ // Forcefully remove the users home directory if it has not already been done
+ // (sometimes deluser doesn't do its job even with the flag)
+ cmd = exec.Command("rm", "-rf", "/home/"+username)
+ cmd.Stderr = &cerr
+
+ if err = cmd.Run(); err != nil {
+ return err, errors.New(cerr.String())
+ }
+
+ return nil, nil
+}