Merge branch 'master' into password-length

This commit is contained in:
Maria Rice 2018-01-09 14:20:16 -06:00
commit f6eefbbfec
12 changed files with 346 additions and 14 deletions

View file

@ -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*

View file

@ -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
```

View file

@ -0,0 +1,6 @@
var sshModal = jQuery(".manage-ssh-modal");
jQuery('._js_manage-ssh').on('click', function(e){
e.preventDefault();
sshModal.modal('show');
});

View 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">&times;</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>

View file

View file

@ -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
View 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
View 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
}

View file

@ -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
View 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
View 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
}