Merge pull request #142 from george-e-shaw-iv/master

Cleaned up bundle creation process and replaced system package with github.com/george-e-shaw-iv/nixtools
This commit is contained in:
George Shaw 2018-04-06 15:45:27 -05:00 committed by GitHub
commit 95d39f27bb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 741 additions and 280 deletions

15
Gopkg.lock generated
View file

@ -13,10 +13,21 @@
revision = "dbeaa9332f19a944acb5736b4456cfcc02140e29"
version = "v3.1.0"
[[projects]]
branch = "master"
name = "github.com/george-e-shaw-iv/nixtools"
packages = ["."]
revision = "a3be4bd706b46bc571b4b34981b06089c6c7158d"
[[projects]]
branch = "master"
name = "golang.org/x/crypto"
packages = ["acme","acme/autocert","bcrypt","blowfish"]
packages = [
"acme",
"acme/autocert",
"bcrypt",
"blowfish"
]
revision = "2509b142fb2b797aa7587dad548f113b2c0f20ce"
[[projects]]
@ -28,6 +39,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "bc99bfe3269f7f4c24a0166bbfe5fd0cafdee6c803274787bff0ac01b8cb4ed6"
inputs-digest = "95bd56d22992e0d2f4bb1959e7b1b38f5c9716c6c643eff6b6da090d29fb1245"
solver-name = "gps-cdcl"
solver-version = 1

View file

@ -1,4 +1,4 @@
package system
package bundle
const DEFAULT_INDEX = `<!DOCTYPE html>
<html lang="en">

View file

@ -13,7 +13,7 @@ import (
"github.com/Ennovar/gPanel/pkg/emailer"
"github.com/Ennovar/gPanel/pkg/encryption"
"github.com/Ennovar/gPanel/pkg/gpaccount"
"github.com/Ennovar/gPanel/pkg/system"
"github.com/george-e-shaw-iv/nixtools"
)
func Create(res http.ResponseWriter, req *http.Request, logger *log.Logger, bundles map[string]*gpaccount.Controller) bool {
@ -37,7 +37,7 @@ func Create(res http.ResponseWriter, req *http.Request, logger *log.Logger, bund
return false
}
// Check if account port is in use by system
/* Check if account port is in use by system */
check, err := net.Listen("tcp", ":"+strconv.Itoa(createBundleRequestData.AccPort))
if err != nil {
logger.Println(req.URL.Path + "::" + "a service is already listening on port " + strconv.Itoa(createBundleRequestData.AccPort) + "::" + err.Error())
@ -46,7 +46,7 @@ func Create(res http.ResponseWriter, req *http.Request, logger *log.Logger, bund
}
check.Close()
// Check if public port is in use by system
/* Check if public port is in use by system */
check, err = net.Listen("tcp", ":"+strconv.Itoa(createBundleRequestData.PubPort))
if err != nil {
logger.Println(req.URL.Path + "::" + "a service is already listening on port " + strconv.Itoa(createBundleRequestData.PubPort) + "::" + err.Error())
@ -55,7 +55,7 @@ func Create(res http.ResponseWriter, req *http.Request, logger *log.Logger, bund
}
check.Close()
// Check if public/account ports are in use by another bundle
/* Check if public/account ports are in use by another bundle */
err = nil
for k, v := range bundles {
if k == createBundleRequestData.Name {
@ -78,11 +78,33 @@ func Create(res http.ResponseWriter, req *http.Request, logger *log.Logger, bund
return false
}
//check mail creds exist and work
//check admin settings are set
//check if can use username
//if those pass, then continue with bundle creation
/* Check if admin settings are set and valid */
ds, err := database.Open("server/" + database.DB_SETTINGS)
if err != nil {
logger.Println(req.URL.Path + "::" + err.Error())
http.Error(res, err.Error(), http.StatusInternalServerError)
return false
}
err = ds.CheckAdminSettings()
if err != nil {
ds.Close()
logger.Println(req.URL.Path + "::" + err.Error())
http.Error(res, err.Error(), http.StatusBadRequest)
return false
}
ds.Close()
/* Check if bundle username can be used within the system */
if userExists := nixtools.UserExists(createBundleRequestData.Name); userExists {
logger.Println(req.URL.Path + "::username already exists under that bundle name within the system")
http.Error(res, err.Error(), http.StatusBadRequest)
return false
}
/* IF ALL CHECKS PASSED, start the bundle creation process */
/* Create the bundle directory within the gPanel directory */
newBundle := "bundles/" + createBundleRequestData.Name
err = os.Mkdir(newBundle, 0777)
if err != nil {
@ -91,6 +113,7 @@ func Create(res http.ResponseWriter, req *http.Request, logger *log.Logger, bund
return false
}
/* Create the log folder within the new bundle directory */
err = os.Mkdir(newBundle+"/logs", 0777)
if err != nil {
logger.Println(req.URL.Path + "::" + err.Error())
@ -98,13 +121,15 @@ func Create(res http.ResponseWriter, req *http.Request, logger *log.Logger, bund
return false
}
ds, err := database.Open(newBundle + "/" + database.DB_MAIN)
/* Create and open the newly created bundle's main datastore */
ds, err = database.Open(newBundle + "/" + database.DB_MAIN)
if err != nil {
logger.Println(req.URL.Path + "::" + err.Error())
http.Error(res, err.Error(), http.StatusInternalServerError)
return false
}
/* Put the new bundle's ports within the datastore */
var databaseBundlePorts struct {
Account int `json:"account"`
Public int `json:"public"`
@ -114,16 +139,19 @@ func Create(res http.ResponseWriter, req *http.Request, logger *log.Logger, bund
err = ds.Put(database.BUCKET_PORTS, []byte("bundle_ports"), databaseBundlePorts)
if err != nil {
ds.Close()
logger.Println(req.URL.Path + "::" + err.Error())
http.Error(res, err.Error(), http.StatusInternalServerError)
return false
}
/* Create the default bundle username and password */
var defaultBundleUser database.Struct_Users
var tempPass = encryption.RandomString(16)
defaultBundleUser.Pass, err = encryption.HashPassword(tempPass)
if err != nil {
ds.Close()
logger.Println(req.URL.Path + "::" + err.Error())
http.Error(res, err.Error(), http.StatusInternalServerError)
return false
@ -133,19 +161,46 @@ func Create(res http.ResponseWriter, req *http.Request, logger *log.Logger, bund
err = ds.Put(database.BUCKET_USERS, []byte("root"), defaultBundleUser)
if err != nil {
ds.Close()
logger.Println(req.URL.Path + "::" + err.Error())
http.Error(res, err.Error(), http.StatusInternalServerError)
return false
}
ds.Close()
err, err2 := system.CreateBundleUser(createBundleRequestData.Name)
/* Create a system user for the new bundle */
sysUser, err := nixtools.GetUser(createBundleRequestData.Name, true)
if err != nil {
logger.Println(req.URL.Path + "::" + err.Error() + " AND " + err2.Error())
http.Error(res, err2.Error(), http.StatusInternalServerError)
logger.Println(req.URL.Path + "::" + err.Error())
http.Error(res, err.Error(), http.StatusInternalServerError)
return false
}
/* Initialize SSH access for the new user and give the root account access */
err = sysUser.InitSSH(true)
if err != nil {
logger.Println(req.URL.Path + "::" + err.Error())
http.Error(res, err.Error(), http.StatusInternalServerError)
return false
}
/* Create document_root within new system user's home directory */
err = sysUser.CreateDirectory("document_root", 0777)
if err != nil {
logger.Println(req.URL.Path + "::" + err.Error())
http.Error(res, err.Error(), http.StatusInternalServerError)
return false
}
/* Write the default index within the document_root */
err = sysUser.WriteFile("document_root/index.html", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777, []byte(DEFAULT_INDEX))
if err != nil {
logger.Println(req.URL.Path + "::" + err.Error())
http.Error(res, err.Error(), http.StatusInternalServerError)
return false
}
/* Add newly created bundle to list of current bundles (run-time list) */
bundles[createBundleRequestData.Name], err = gpaccount.New(newBundle+"/", createBundleRequestData.Name, databaseBundlePorts.Account, databaseBundlePorts.Public)
if err != nil {
@ -154,9 +209,22 @@ func Create(res http.ResponseWriter, req *http.Request, logger *log.Logger, bund
return false
}
_ = bundles[createBundleRequestData.Name].Start()
_ = bundles[createBundleRequestData.Name].Public.Start()
/* Start the account server and public server of the newly created bundle */
err = bundles[createBundleRequestData.Name].Start()
if err != nil {
logger.Println(req.URL.Path + "::" + err.Error())
http.Error(res, err.Error(), http.StatusInternalServerError)
return false
}
err = bundles[createBundleRequestData.Name].Public.Start()
if err != nil {
logger.Println(req.URL.Path + "::" + err.Error())
http.Error(res, err.Error(), http.StatusInternalServerError)
return false
}
/* Get SMTP and Admin general settings for use in the email to be sent to new bundle designated email */
ds, err = database.Open("server/" + database.DB_SETTINGS)
if err != nil {
logger.Println(req.URL.Path + "::" + err.Error())
@ -182,6 +250,7 @@ func Create(res http.ResponseWriter, req *http.Request, logger *log.Logger, bund
return false
}
/* Send email with information about system and bundle account to designated bundle email AND gPanel admin */
mail, err := emailer.New(smtpSettings.Type, emailer.Credentials{
Username: smtpSettings.Username,
Password: smtpSettings.Password,

View file

@ -9,7 +9,7 @@ import (
"github.com/Ennovar/gPanel/pkg/database"
"github.com/Ennovar/gPanel/pkg/gpaccount"
"github.com/Ennovar/gPanel/pkg/system"
"github.com/george-e-shaw-iv/nixtools"
)
func Delete(res http.ResponseWriter, req *http.Request, logger *log.Logger, bundles map[string]*gpaccount.Controller, dir string) bool {
@ -63,10 +63,17 @@ func Delete(res http.ResponseWriter, req *http.Request, logger *log.Logger, bund
delete(bundles, rqData.Name)
err, err2 := system.DeleteBundleUser(rqData.Name)
sysUser, err := nixtools.GetUser(rqData.Name, false)
if err != nil {
logger.Println(req.URL.Path + "::" + err.Error() + " AND " + err2.Error())
http.Error(res, err2.Error(), http.StatusInternalServerError)
logger.Println(req.URL.Path + "::" + err.Error())
http.Error(res, err.Error(), http.StatusInternalServerError)
return false
}
err = sysUser.Delete(true, false)
if err != nil {
logger.Println(req.URL.Path + "::" + err.Error())
http.Error(res, err.Error(), http.StatusInternalServerError)
return false
}

View file

@ -6,7 +6,7 @@ import (
"net/http"
"strconv"
"github.com/Ennovar/gPanel/pkg/system"
"github.com/george-e-shaw-iv/nixtools"
)
func AddKey(res http.ResponseWriter, req *http.Request, logger *log.Logger) bool {
@ -28,7 +28,14 @@ func AddKey(res http.ResponseWriter, req *http.Request, logger *log.Logger) bool
return false
}
if err = system.AddAuthorizedKey(requestData.Username, requestData.PublicKey); err != nil {
sysUser, err := nixtools.GetUser(requestData.Username, false)
if err != nil {
logger.Println(req.URL.Path + "::" + err.Error())
http.Error(res, err.Error(), http.StatusBadRequest)
return false
}
if err = sysUser.AddAuthorizedKey(requestData.PublicKey); err != nil {
logger.Println(req.URL.Path + "::" + err.Error())
http.Error(res, err.Error(), http.StatusInternalServerError)
return false

View file

@ -6,7 +6,7 @@ import (
"net/http"
"strconv"
"github.com/Ennovar/gPanel/pkg/system"
"github.com/george-e-shaw-iv/nixtools"
)
func DeleteKey(res http.ResponseWriter, req *http.Request, logger *log.Logger) bool {
@ -28,7 +28,14 @@ func DeleteKey(res http.ResponseWriter, req *http.Request, logger *log.Logger) b
return false
}
if err = system.DeleteAuthorizedKey(requestData.Username, requestData.PublicKey); err != nil {
sysUser, err := nixtools.GetUser(requestData.Username, false)
if err != nil {
logger.Println(req.URL.Path + "::" + err.Error())
http.Error(res, err.Error(), http.StatusBadRequest)
return false
}
if err = sysUser.DeleteAuthorizedKey(requestData.PublicKey); err != nil {
logger.Println(req.URL.Path + "::" + err.Error())
http.Error(res, err.Error(), http.StatusInternalServerError)
return false

View file

@ -6,7 +6,7 @@ import (
"net/http"
"strconv"
"github.com/Ennovar/gPanel/pkg/system"
"github.com/george-e-shaw-iv/nixtools"
)
func GetKeys(res http.ResponseWriter, req *http.Request, logger *log.Logger) bool {
@ -27,8 +27,15 @@ func GetKeys(res http.ResponseWriter, req *http.Request, logger *log.Logger) boo
return false
}
sysUser, err := nixtools.GetUser(requestData.Username, false)
if err != nil {
logger.Println(req.URL.Path + "::" + err.Error())
http.Error(res, err.Error(), http.StatusBadRequest)
return false
}
var keys []string
if keys, err = system.GetAuthorizedKeys(requestData.Username); err != nil {
if keys, err = sysUser.GetAuthorizedKeys(true); err != nil {
logger.Println(req.URL.Path + "::" + err.Error())
http.Error(res, err.Error(), http.StatusInternalServerError)
return false

View file

@ -1,5 +1,13 @@
package database
import (
"encoding/json"
"errors"
"github.com/Ennovar/gPanel/pkg/emailer"
"github.com/boltdb/bolt"
)
type Struct_SMTP struct {
Type string `json:"type"`
Username string `json:"username"`
@ -9,6 +17,48 @@ type Struct_SMTP struct {
}
type Struct_Admin struct {
Name string `json:"name"`
Name string `json:"name"`
Email string `json:"email"`
}
}
// Function CheckAdminSettings makes sure that the
// admin settings are set and are valid.
func (ds *Datastore) CheckAdminSettings() (rerr error) {
rerr = ds.handle.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(BUCKET_GENERAL))
smtp := b.Get([]byte("smtp"))
var smtpCreds Struct_SMTP
err := json.Unmarshal(smtp, &smtpCreds)
if err != nil {
return err
}
_, err = emailer.New(smtpCreds.Type, emailer.Credentials{
Username: smtpCreds.Username,
Password: smtpCreds.Password,
Server: smtpCreds.Server,
Port: smtpCreds.Port,
})
if err != nil {
return err
}
a := b.Get([]byte("admin"))
var admin Struct_Admin
err = json.Unmarshal(a, &admin)
if err != nil {
return err
}
if len(admin.Email) == 0 || len(admin.Name) == 0 {
return errors.New("admin name and email settings are empty")
}
return nil
})
return
}

View file

@ -72,3 +72,7 @@ func (e *Emailer) SendCustom(to string, msg []byte) error {
return nil
}
func (e *Emailer) Test() {
}

View file

@ -1,73 +0,0 @@
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 + "\n"); 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
}
func GetAuthorizedKeys(username string) ([]string, error) {
f, err := ioutil.ReadFile("/home/" + username + "/.ssh/authorized_keys")
if err != nil {
return nil, err
}
// Remove empty strings
raw := strings.Split(string(f), "\n")
var clean []string
for _, each := range raw {
if each != "" {
clean = append(clean, each)
}
}
// Remove root key
if len(clean) == 1 {
return nil, nil
}
return clean[1:], nil
}

View file

@ -1,178 +0,0 @@
package system
import (
"bytes"
"errors"
"fmt"
"os"
"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())
}
// Add default index page
f, err := os.OpenFile("/home/"+username+"/document_root/index.html", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777)
if err != nil {
return err, nil
}
defer f.Close()
fmt.Fprintln(f, DEFAULT_INDEX)
/* 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())
}
cmd = exec.Command("chown", username+":", "/home/"+username+"/document_root")
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
}

View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 George Shaw
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,4 @@
# nixtools
A golang toolkit for linux system operations.
The GoDoc reference for `george-e-shaw-iv/nixtools` is located [here](https://godoc.org/github.com/george-e-shaw-iv/nixtools).

16
vendor/github.com/george-e-shaw-iv/nixtools/.gitignore generated vendored Normal file
View file

@ -0,0 +1,16 @@
# Binaries for programs and plugins
*.exe
*.dll
*.so
*.dylib
nixtools
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
.glide/
.idea/

View file

@ -0,0 +1,10 @@
// Package nixtools is a toolkit for various linux
// system operations.
package nixtools
// Type user is a struct that has functions attached to
// it that require a username to properly carry out.
type User struct {
ID int
Name string
}

215
vendor/github.com/george-e-shaw-iv/nixtools/ssh.go generated vendored Normal file
View file

@ -0,0 +1,215 @@
package nixtools
import (
"os/exec"
"bytes"
"errors"
"os"
"io/ioutil"
"strings"
)
// Function InitSSH creates the necessary folders,
// files, and generates a default key-pair for the
// given user. If parameter rootHasAccess is set
// to true then the public key of the root (sudo) user
// will be copied into the authorized_keys file of
// the user.
func (u *User) InitSSH(rootHasAccess bool) (error) {
var stderr bytes.Buffer
keygenArgs := []string{
"-t",
"rsa",
"-N",
"",
"-f",
"/home/" + u.Name + "/.ssh/id_rsa",
}
// Create the .ssh folder for said user
cmd := exec.Command("mkdir", "/home/"+u.Name+"/.ssh")
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return errors.New(stderr.String())
}
// Create authorized_keys file
cmd = exec.Command("touch", "/home/"+u.Name+"/.ssh/authorized_keys")
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return errors.New(stderr.String())
}
if rootHasAccess {
// Put root public key into authorized_keys file
cmd = exec.Command("cp", "/root/.ssh/id_rsa.pub", "/home/"+u.Name+"/.ssh/authorized_keys")
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return errors.New(stderr.String())
}
}
// Create the default key-pair for said user
cmd = exec.Command("ssh-keygen", keygenArgs...)
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return errors.New(stderr.String())
}
/* OWNERSHIP AND FILE PERMISSIONS START */
cmd = exec.Command("chmod", "700", "/home/"+u.Name+"/.ssh")
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return errors.New(stderr.String())
}
cmd = exec.Command("chmod", "600", "/home/"+u.Name+"/.ssh/id_rsa")
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return errors.New(stderr.String())
}
cmd = exec.Command("chmod", "644", "/home/"+u.Name+"/.ssh/id_rsa.pub")
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return errors.New(stderr.String())
}
cmd = exec.Command("chmod", "644", "/home/"+u.Name+"/.ssh/authorized_keys")
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return errors.New(stderr.String())
}
cmd = exec.Command("chown", u.Name+":", "/home/"+u.Name+"/.ssh")
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return errors.New(stderr.String())
}
cmd = exec.Command("chown", u.Name+":", "/home/"+u.Name+"/.ssh/id_rsa")
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return errors.New(stderr.String())
}
cmd = exec.Command("chown", u.Name+":", "/home/"+u.Name+"/.ssh/id_rsa.pub")
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return errors.New(stderr.String())
}
cmd = exec.Command("chown", u.Name+":", "/home/"+u.Name+"/.ssh/authorized_keys")
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return errors.New(stderr.String())
}
/* OWNERSHIP AND FILE PERMISSIONS END */
return nil
}
// Function AddAuthorizedKey adds a new public key to
// a given user's authorized_keys file.
func (u *User) AddAuthorizedKey(key string) error {
f, err := os.OpenFile("/home/"+u.Name+"/.ssh/authorized_keys", os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
return err
}
if _, err = f.WriteString(key + "\n"); err != nil {
return err
}
return nil
}
// Function DeleteAuthorizedKey removes a public key
// that is already in the authorized_keys file of
// a given user.
func (u *User) DeleteAuthorizedKey(key string) error {
old, err := ioutil.ReadFile("/home/" + u.Name + "/.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/"+u.Name+"/.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
}
// Function GetAuthorizedKeys will return a slice
// of strings that contains all of the public keys
// within a given user's authorized_keys file.
// If the parameter removeRootKey is set to true the
// public key of the current root user of the system,
// if found within the file, will not be placed within
// the slice of strings.
func (u *User) GetAuthorizedKeys(removeRootKey bool) ([]string, error) {
f, err := ioutil.ReadFile("/home/" + u.Name + "/.ssh/authorized_keys")
if err != nil {
return nil, err
}
// Remove empty strings
raw := strings.Split(string(f), "\n")
var clean []string
for _, each := range raw {
if each != "" {
clean = append(clean, each)
}
}
// Remove root key if necessary
if removeRootKey {
if len(clean) == 1 {
return nil, nil
}
key, err := ioutil.ReadFile("/root/.ssh/id_rsa.pub")
if err != nil {
return nil, err
}
for k, v := range clean {
if v == string(key) {
clean = append(clean[:k], clean[k+1:]...)
break
}
}
}
return clean, nil
}

284
vendor/github.com/george-e-shaw-iv/nixtools/users.go generated vendored Normal file
View file

@ -0,0 +1,284 @@
package nixtools
import (
"os/exec"
"bytes"
"errors"
"strconv"
"io"
"os"
"strings"
)
// Function GetUser takes either an ID or Username
// as an identifier and returns a type User associated
// with the given identifier. createIfNotExist parameter
// can only be utilized if the function is given a
// username, not an ID.
func GetUser(identifier interface{}, createIfNotExist bool) (*User, error) {
var u User
var err error
if id, ok := identifier.(int); ok {
u.ID = id
u.Name, err = getUsername(id)
if err == nil {
return &u, nil
}
return nil, err
} else if name, ok := identifier.(string); ok {
u.Name = name
u.ID, err = getUserID(name)
if err == nil {
return &u, nil
}
if createIfNotExist {
u.ID, err = createUser(name)
if err != nil {
return nil, err
}
return &u, nil
}
return nil, err
}
return nil, errors.New("identifier must be a valid user id (int) or username (string)")
}
// Function CreateDirectory attempts to create a directory along
// with all of its parent directories (if needed) relative to the
// user's home directory. The perm parameter is in the format of linux
// file permissions (e.g. 0664).
func (u *User) CreateDirectory(dirPath string, perm os.FileMode) (error) {
return os.MkdirAll("/home/"+u.Name+"/"+dirPath, perm)
}
// Function WriteFile writes to a file with a given mode that
// is set by flags (flag parameter) that are defined within the
// os package. The filePath is relative to the current user's
// home directory. The perm parameter is in the format of linux
// file permissions (e.g. 0664).
func (u *User) WriteFile(filePath string, flag int, perm os.FileMode, content []byte) (error) {
f, err := os.OpenFile("/home/"+u.Name+"/"+filePath, flag, perm)
if err != nil {
return err
}
defer f.Close()
n, err := f.Write(content)
if err != nil {
return err
}
if n != len(content) {
f.Close()
_ = os.Remove("/home/"+u.Name+"/"+filePath)
return errors.New("full length of content was not able to be written, file was attempted to be deleted")
}
return nil
}
// Function DeleteFileOrDirectory attempts to delete a named
// file or directory relative to the user's home directory.
func (u *User) DeleteFileOrDirectory(path string) (error) {
return os.RemoveAll("/home/"+u.Name+"/"+path)
}
// Function Delete will attempt to delete the given user
// attached to the struct. deleteOwnedFiles parameter
// will remove all files owned by the user and
// removeHome parameter will delete the home directory
// of the user. Warning: parameter deleteOwnedFiles will
// cause the function to take awhile to execute, consider
// using a goroutine to prevent your program from stalling.
func (u *User) Delete(removeHome, deleteOwnedFiles bool) (error) {
var stderr bytes.Buffer
var args = []string{
"--quiet",
}
if deleteOwnedFiles {
args = append(args, "--remove-all-files")
}
if removeHome {
args = append(args, "--remove-home")
}
args = append(args, u.Name)
// Delete the user and try to remove all files associated
cmd := exec.Command("deluser", args...)
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return errors.New(stderr.String())
}
// Forcefully remove the users home directory as sometimes
// the --remove-home or --remove-all-files flags don't work
if deleteOwnedFiles || removeHome {
cmd = exec.Command("rm", "-rf", "/home/"+u.Name)
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return errors.New(stderr.String())
}
}
return nil
}
// Function SetPassword sets the users password
// to the given parameter.
func (u *User) SetPassword(password string) (error) {
return nil
}
// Function ForcePasswordChange forces a user to change
// their password upon next login.
func (u *User) ForcePasswordChange() (error) {
cmd := exec.Command("chage", "-d", "0", u.Name)
var stderr bytes.Buffer
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return errors.New(stderr.String())
}
return nil
}
// Function Lock lock's a users account, rendering them
// unable to login through any means of authentication.
func (u *User) Lock() (error) {
cmd := exec.Command("chage", "-E", "0", u.Name)
var stderr bytes.Buffer
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return errors.New(stderr.String())
}
return nil
}
// Function Unlock will unlock a users account, allowing
// them to authenticate through all previous mediums.
func (u *User) Unlock() (error) {
cmd := exec.Command("chage", "-E", "-1", u.Name)
var stderr bytes.Buffer
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return errors.New(stderr.String())
}
return nil
}
// Function getUserID returns the ID associated with
// a username if it exists or -1 and an error if the
// given username does not exist.
func getUserID(username string) (int, error) {
cmd := exec.Command("id", "-u", username)
var stderr, stdout bytes.Buffer
cmd.Stderr = &stderr
cmd.Stdout = &stdout
if err := cmd.Run(); err != nil {
return -1, errors.New(stderr.String())
}
stdoutParsed := strings.Replace(stdout.String(), "\n", "", -1)
id, err := strconv.Atoi(stdoutParsed)
if err != nil {
return -1, err
}
return id, nil
}
// Function getUsername returns the username associated
// with an ID if it exists.
func getUsername(id int) (string, error) {
var stderr, stdout bytes.Buffer
reader, writer := io.Pipe()
cmdOut := exec.Command("getent", "passwd", strconv.Itoa(id))
cmdOut.Stdout = writer
cmdIn := exec.Command("cut", "-d:", "-f1")
cmdIn.Stdin = reader
cmdIn.Stdout = &stdout
cmdIn.Stderr = &stderr
cmdOut.Start()
cmdIn.Start()
cmdOut.Wait()
writer.Close()
cmdIn.Wait()
reader.Close()
if len(stderr.Bytes()) > 0 {
return "", errors.New(stderr.String())
}
return stdout.String(), nil
}
// Function userExists returns true if the given
// username exists within the system or false if
// the given username does not exist.
func UserExists(username string) (bool) {
cmd := exec.Command("id", "-u", username)
if err := cmd.Run(); err != nil {
return false
}
return true
}
// Function createUser will attempt to create a
// new user in the system with the given username.
// Upon success will return the new ID of the
// created user.
func createUser(username string) (int, error) {
var stderr bytes.Buffer
args := []string{
"--disabled-password",
"--gecos",
"",
username,
}
cmd := exec.Command("adduser", args...)
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return 0, errors.New(stderr.String())
}
id, err := getUserID(username)
if err != nil {
return 0, err
}
return id, nil
}