mirror of
https://github.com/donl/gPanel.git
synced 2026-05-25 22:06:55 -06:00
Merge branch 'master' into password-length
This commit is contained in:
commit
f6eefbbfec
12 changed files with 346 additions and 14 deletions
|
|
@ -70,7 +70,7 @@ Key: __Implemented__ | __*Implemented, but needs work*__ | *Unimplemented* | ~~R
|
|||
* Clients
|
||||
* __Multi-client support__
|
||||
* __Configuration of new clients__
|
||||
* __*Configuration of existing hosts*__
|
||||
* __Configuration of existing hosts__
|
||||
* gPanel Account (Upper level of bundles)
|
||||
* Accessibility
|
||||
* __Using ports defined during configuration__
|
||||
|
|
@ -89,12 +89,12 @@ Key: __Implemented__ | __*Implemented, but needs work*__ | *Unimplemented* | ~~R
|
|||
* *Alert system for fatal errors*
|
||||
* Mail Servers
|
||||
* Domains
|
||||
* *Configuration of domains*
|
||||
* __Configuration of domains__
|
||||
* *Configuration of sub-domains*
|
||||
* *TLS/SSL certificate support*
|
||||
* *Multi-domain support*
|
||||
* __Multi-domain support__
|
||||
* Remote Access
|
||||
* *SSH Access*
|
||||
* __*SSH Access*__
|
||||
* File Manager
|
||||
* *CRUD*
|
||||
* *Inline Editor*
|
||||
|
|
@ -107,15 +107,15 @@ Key: __Implemented__ | __*Implemented, but needs work*__ | *Unimplemented* | ~~R
|
|||
* Supported content types
|
||||
* __All of the obvious ones (.jpg/.html/.css/etc)__
|
||||
* *.go*
|
||||
* *.php*
|
||||
* __*.php*__
|
||||
* General
|
||||
* Deployment
|
||||
* *Binary*
|
||||
* __Binary__
|
||||
* *GUI Installation Helper*
|
||||
* Multi-infrastructure Support
|
||||
* __Windows__
|
||||
* ~~Windows~~ (No native solution for SSH exists within Windows OS)
|
||||
* __Linux__
|
||||
* __Mac__
|
||||
* *Mac* (This is possible, but OS detection and refactoring within the system package needs done)
|
||||
* *Amazon Web Services*
|
||||
* *DigitalOcean*
|
||||
* *Google Cloud Platform*
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ A web-hosting control panel written in Go.
|
|||
|
||||
## Stack
|
||||
|
||||
Backend: __[Go](https://golang.org/)__
|
||||
Backend: __[Go (1.8+)](https://golang.org/)__
|
||||
Database: __[Bolt](https://github.com/boltdb/bolt)__
|
||||
CSS Toolkit(s): __[Bootstrap 4](http://getbootstrap.com/) & [Font Awesome](http://fontawesome.io/)__
|
||||
JS Toolkit(s): __[jQuery](https://jquery.com/)__
|
||||
|
|
@ -35,6 +35,9 @@ git remote rename fork origin
|
|||
To deploy...
|
||||
|
||||
```shell
|
||||
# Starting gPanel
|
||||
go run main.go
|
||||
# Build the binary
|
||||
go build gpanel.go
|
||||
|
||||
# Execute binary as root (root access is needed for functions within the system package)
|
||||
sudo ./gpanel
|
||||
```
|
||||
|
|
|
|||
6
account/assets/js/panelHandlers/users/addkey.js
Normal file
6
account/assets/js/panelHandlers/users/addkey.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
var sshModal = jQuery(".manage-ssh-modal");
|
||||
|
||||
jQuery('._js_manage-ssh').on('click', function(e){
|
||||
e.preventDefault();
|
||||
sshModal.modal('show');
|
||||
});
|
||||
0
account/assets/js/panelHandlers/users/deletekey.js
Normal file
0
account/assets/js/panelHandlers/users/deletekey.js
Normal file
|
|
@ -226,6 +226,26 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SSH Management Modal -->
|
||||
<div class="modal fade manage-ssh-modal" tabindex="-1" role="dialog" aria-labelledby="manage-ssh-modal" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">SSH Management</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Todo: use /api/ssh/addkey and /api/ssh/deletekey in some way in this modal</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
|
|
@ -306,9 +326,10 @@
|
|||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">Account Users</h4>
|
||||
<h6 class="card-subtitle mb-4 text-muted">View, edit, update, and remove users that can access this gPanel Account</h6>
|
||||
<h6 class="card-subtitle mb-4 text-muted">View, edit, update, and remove users that can access the gPanel Account and utilize SSH.</h6>
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-outline-primary _js_manage-users">Manage Users</button>
|
||||
<button type="button" class="btn btn-outline-primary _js_manage-ssh">Manage SSH</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -370,6 +391,8 @@
|
|||
<script type="text/javascript" src="assets/js/panelHandlers/users/new.js"></script>
|
||||
<script type="text/javascript" src="assets/js/panelHandlers/users/delete.js"></script>
|
||||
<script type="text/javascript" src="assets/js/panelHandlers/users/new_password.js"></script>
|
||||
<script type="text/javascript" src="assets/js/panelHandlers/users/addkey.js"></script>
|
||||
<script type="text/javascript" src="assets/js/panelHandlers/users/deletekey.js"></script>
|
||||
|
||||
<script type="text/javascript" src="assets/js/panelHandlers/domains_subdomains/domain_management.js"></script>
|
||||
<script type="text/javascript" src="assets/js/panelHandlers/domains_subdomains/domain_link.js"></script>
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
39
pkg/api/ssh/addkey.go
Normal file
39
pkg/api/ssh/addkey.go
Normal file
|
|
@ -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
|
||||
}
|
||||
39
pkg/api/ssh/deletekey.go
Normal file
39
pkg/api/ssh/deletekey.go
Normal file
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
49
pkg/system/keys.go
Normal file
49
pkg/system/keys.go
Normal file
|
|
@ -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
|
||||
}
|
||||
160
pkg/system/user.go
Normal file
160
pkg/system/user.go
Normal file
|
|
@ -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
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue