cisco

Home Shitty cloud provider
Log | Files | Refs | Submodules | git clone https://git.ne02ptzero.me/git/cisco

commit c92cbc1c7a85c135c6719865efb55a578194dd00
parent 54e7de1cdc80ef68d03be5518ed627162d05336a
Author: Louis Solofrizzo <louis@ne02ptzero.me>
Date:   Sat, 30 Mar 2019 15:16:59 +0100

Api: Add API

Signed-off-by: Louis Solofrizzo <louis@ne02ptzero.me>

Diffstat:
Mapi/CMakeLists.txt | 2++
Dapi/cisco.conf | 7-------
Mapi/config.go | 62++++++++++++++++++++++++++++++++++++++++----------------------
Aapi/http.go | 76++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mapi/instance.go | 225++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Mapi/main.go | 116++++++++++++++++++++++++++++++++++---------------------------------------------
Aapi/slaves.go | 322+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mapi/ssh.go | 191++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Mapi/user.go | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
9 files changed, 924 insertions(+), 147 deletions(-)

diff --git a/api/CMakeLists.txt b/api/CMakeLists.txt @@ -4,4 +4,6 @@ add_go_component(lf-cloud-api instance.go config.go ssh.go + slaves.go + http.go ) diff --git a/api/cisco.conf b/api/cisco.conf @@ -1,7 +0,0 @@ -cert: /home/louis/Work/perso/api_cloud.crt -key: /home/louis/Work/perso/api_cloud.key -host: 127.0.0.1 -port: 8080 -ca: /home/louis/Work/perso/ca.crt -sshproxy: cloud.louifox.house -sshkey: /home/louis/Work/perso/cisco.key diff --git a/api/config.go b/api/config.go @@ -1,35 +1,53 @@ package main import ( - "flag" - "io/ioutil" + "flag" + "github.com/go-xorm/xorm" + "io/ioutil" - "gopkg.in/yaml.v2" + log "github.com/sirupsen/logrus" + + _ "github.com/mattn/go-sqlite3" + "gopkg.in/yaml.v2" ) type CiscoConfig struct { - Cert string `yaml:"cert" binding:"required"` - Key string `yaml:"key" binding:"required"` - Host string `yaml:"host" binding:"required"` - Port string `yaml:"port" binding:"required"` - CA string `yaml:"ca" binding:"required"` - SSHProxy string `yaml:"sshproxy" binding:"required"` - SSHKey string `yaml:"sshkey" binding:"required"` + Cert string `yaml:"cert" binding:"required"` + Key string `yaml:"key" binding:"required"` + Host string `yaml:"host" binding:"required"` + Port string `yaml:"port" binding:"required"` + CA string `yaml:"ca" binding:"required"` + SSHProxy string `yaml:"sshproxy" binding:"required"` + SSHKey string `yaml:"sshkey" binding:"required"` + DB string `yaml:"db" binding:"required"` + Slaves []string `yaml:"slaves" binding:"required"` } var Config CiscoConfig +var Database *xorm.Engine func setConfig() error { - filename := flag.String("config", "/etc/cisco.conf", "The yaml configuration file") - flag.Parse() - - source, err := ioutil.ReadFile(*filename) - if err != nil { - return err - } - err = yaml.Unmarshal(source, &Config) - if err != nil { - return err - } - return nil + filename := flag.String("config", "/etc/cisco.conf", "The yaml configuration file") + flag.Parse() + + source, err := ioutil.ReadFile(*filename) + if err != nil { + return err + } + err = yaml.Unmarshal(source, &Config) + if err != nil { + return err + } + + Database, err = xorm.NewEngine("sqlite3", Config.DB) + if err != nil { + return err + } + + err = Database.Sync2(new(Instance)) + if err != nil { + log.Fatal(err) + } + + return nil } diff --git a/api/http.go b/api/http.go @@ -0,0 +1,76 @@ +package main + +import ( + log "github.com/sirupsen/logrus" + "os" + "os/exec" + "text/template" +) + +type InstanceHTTP struct { + Domain string + Address string + Port int +} + +func addInstanceToNginx(instance *Instance, domain string) error { + tmpl, err := template.New("nginx-conf").Parse(` + server { + server_name {{.Domain}}; + set $upstream {{.Address}}:{{.Port}}; + location / { + proxy_pass_header Authorization; + proxy_pass http://$upstream; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_http_version 1.1; + proxy_set_header Connection ""; + proxy_buffering off; + client_max_body_size 0; + proxy_read_timeout 36000s; + proxy_redirect off; + } + } + `) + + if err != nil { + log.Errorf("Error in templating: %s", err) + return err + } + + bind, err := instanceBindPort(instance, "80") + if err != nil { + log.Errorf("Cannot bind port on instance") + return err + } + + template := InstanceHTTP{ + Domain: domain, + Address: bind.IP, + Port: bind.Port, + } + + file, err := os.Create("/etc/nginx/conf.d/" + domain + ".conf") + if err != nil { + log.Errorf("Cannot open nginx file") + return err + } + + err = tmpl.Execute(file, template) + if err != nil { + log.Errorf("Cannot write to configuration file") + return err + } + + run := exec.Command("/bin/bash", "-c", "certbot -d "+domain+" -n --nginx --redirect") + run.Run() + return nil +} + +func removeInstanceFromNginx(instance *Instance) error { + os.Remove("/etc/nginx/conf.d/" + instance.Name + ".cloud.louifox.house.conf") + run := exec.Command("/bin/bash", "-c", "systemctl reload nginx") + run.Run() + return nil +} diff --git a/api/instance.go b/api/instance.go @@ -1,35 +1,232 @@ package main import ( - "crypto/tls" - "github.com/kataras/iris" + "github.com/kataras/iris" + log "github.com/sirupsen/logrus" + "strconv" + "time" +) + +type InstanceStatus int + +const ( + InstanceCreated InstanceStatus = iota + InstanceAllocated + InstanceStarted + InstanceInstalling + InstanceStopped + InstanceDeleting ) type Instance struct { - Name string `json:"name"` - Architecture string `json:"arch"` - OS string `json:"os"` + ID int64 `json:"id"` + Name string `json:"name" xorm:"varchar(200)"` + Architecture string `json:"arch" xorm:"varchar(200)"` + Release string `json:"release" xorm:"varchar(200)"` + OS string `json:"os" xorm:"varchar(200)"` + PrivateIp bool `json:"private_ip" xorm:"bool"` + Owner string `json:"owner" xorm:"varchar(200)"` + Status InstanceStatus `json:"status" xorm:"int"` + TextStatus string `json:"text_status"` + IPv4 string `json:"ipv4"` + IPv6 string `json:"ipv6"` + Gateway string `json:"gateway"` + Capacity int64 `json:"capacity"` + PhysicalNode string `json:"physical_node" xorm:"varchar(120)"` + CreatedAt time.Time `json:"created" xorm:"created"` + UpdatedAt time.Time `json:"updated" xorm:"updated"` } func instance_list(ctx iris.Context) { + var cert = get_certificate(ctx) + var instances []Instance + + err := Database.Where("owner = ?", cert.Subject.CommonName).Find(&instances) + if err == nil { + ctx.JSON(&instances) + } } func instance_add(ctx iris.Context) { - var instance Instance + var cert = get_certificate(ctx) + var instance Instance + + ctx.ReadJSON(&instance) + instance.Owner = cert.Subject.CommonName + instance.Status = InstanceCreated - ctx.ReadJSON(&instance) - ctx.Writef("%s - %s - %s", instance.Name, instance.Architecture, instance.OS) + Database.Insert(&instance) + ctx.ContentType("application/json") + ctx.Writef("{\"id\": %d}", instance.ID) + go instance_create(&instance) } func instance_exists(ctx iris.Context) { - var tls *tls.ConnectionState + name := ctx.Params().Get("name") - tls = ctx.Request().TLS - //log.Infof("%s", tls.PeerCertificates[0].Subject) - name := ctx.Params().Get("name") - ctx.Writef("Instance %s do exist, and you are %s", name, tls.PeerCertificates[0].Subject.CommonName) + instance := Instance{Name: name} + ctx.ContentType("application/json") + if ok, _ := Database.Get(&instance); ok { + ctx.Writef("{\"status\":\"found\"}") + } else { + ctx.Writef("{\"status\":\"notfound\"}") + } } func instance_get(ctx iris.Context) { - + id, _ := ctx.Params().GetInt64("id") + + instance := Instance{ID: id} + ok, _ := Database.Get(&instance) + + switch instance.Status { + case InstanceCreated: + instance.TextStatus = "created" + case InstanceAllocated: + instance.TextStatus = "allocated" + case InstanceInstalling: + instance.TextStatus = "installing" + case InstanceStarted: + instance.TextStatus = "running" + case InstanceStopped: + instance.TextStatus = "stopped" + case InstanceDeleting: + instance.TextStatus = "deleting" + } + + if ok { + if instance.Status == InstanceStarted || instance.Status == InstanceStopped { + err := instanceGet(&instance) + if err != nil { + ctx.StatusCode(iris.StatusInternalServerError) + return + } + } + ctx.JSON(&instance) + } else { + ctx.StatusCode(iris.StatusNotFound) + } +} + +func instance_delete(ctx iris.Context) { + id, _ := ctx.Params().GetInt64("id") + + instance := Instance{ID: id} + ok, _ := Database.Get(&instance) + + if !ok { + ctx.StatusCode(iris.StatusNotFound) + return + } + + err := instanceDestroy(&instance) + if err == nil { + ssh_rm_host(instance.Name) + removeInstanceFromNginx(&instance) + Database.Delete(&instance) + } +} + +func instance_network(ctx iris.Context) { + id, _ := ctx.Params().GetInt64("id") + + instance := Instance{ID: id} + ok, _ := Database.Get(&instance) + + if !ok { + ctx.StatusCode(iris.StatusNotFound) + return + } + + network, err := instanceGetNetwork(&instance) + if err != nil { + ctx.StatusCode(iris.StatusInternalServerError) + return + } + + ctx.ContentType("application/json") + ctx.Writef("%s", string(network)) +} + +type InstanceBindPost struct { + Source string `json:"port"` + IsLocal bool `json:"local"` +} + +func instance_bind(ctx iris.Context) { + var bind InstanceBindPost + var err error + + id, _ := ctx.Params().GetInt64("id") + + instance := Instance{ID: id} + ok, _ := Database.Get(&instance) + + if !ok { + ctx.StatusCode(iris.StatusNotFound) + return + } + + if err = ctx.ReadJSON(&bind); err != nil { + ctx.StatusCode(iris.StatusBadRequest) + return + } + + if bind.IsLocal { + _, err = instanceBindLocalPort(&instance, bind.Source) + } else { + _, err = instanceBindPort(&instance, bind.Source) + } + + if err != nil { + ctx.StatusCode(iris.StatusInternalServerError) + } else { + ctx.StatusCode(iris.StatusAccepted) + } + +} + +func instance_create(instance *Instance) { + log.Infof("Choosing slave for instance %s", instance.Name) + err := chooseSlave(instance) + if err != nil { + return + } + + err = createInstance(instance) + if err != nil { + return + } + + err = startInstance(instance) + if err != nil { + return + } + + /* Wait for DNS & Internet */ + time.Sleep(5 * time.Second) + + err = instanceInstallAnsible(instance) + if err != nil { + log.Errorf("Could not install Ansible and init playbook") + return + } + + bind, err := instanceBindPort(instance, "22") + if err != nil { + log.Errorf("Could not bind port") + return + } + + err = ssh_add_host(instance.Name, instance.Owner, bind.IP+":"+strconv.Itoa(bind.Port)) + if err != nil { + return + } + + err = addInstanceToNginx(instance, instance.Name+".cloud.louifox.house") + if err != nil { + return + } + + log.Infof("%v", bind) } diff --git a/api/main.go b/api/main.go @@ -1,76 +1,60 @@ package main import ( - "crypto/rand" - "crypto/tls" - "crypto/x509" - "io/ioutil" + "crypto/x509" + "encoding/pem" + "net/url" - log "github.com/sirupsen/logrus" + log "github.com/sirupsen/logrus" - "github.com/kataras/iris" + "github.com/kataras/iris" ) -func main() { - app := iris.New() - app.Use(func(ctx iris.Context) { - log.WithFields(log.Fields{ - "ip": ctx.RemoteAddr(), - "path": ctx.Path(), - "method": ctx.Method(), - }).Info("api request") - ctx.Next() - }) - - err := setConfig() - if err != nil { - panic(err) - } - - app.Get("/user/keys", user_keys) - app.Get("/user/init", user_init) - app.Post("/user/add_key", user_add_key) - app.Get("/instance/list", instance_list) - app.Get("/instance/exists/{name:string}", instance_exists) - app.Post("/instance/add", instance_add) - app.Get("/instance/{name:string}", instance_get) - - /* TLS */ - cert, err := tls.LoadX509KeyPair(Config.Cert, Config.Key) - if err != nil { - log.Fatalf("Failed loading x509 key pair: %s", err) - } - - certpool := x509.NewCertPool() - pem, err := ioutil.ReadFile(Config.CA) - if err != nil { - log.Fatalf("Failed to read client certificate authority: %v", err) - } - if !certpool.AppendCertsFromPEM(pem) { - log.Fatalf("Can't parse client certificate authority") - } - - tlsconfig := tls.Config{ - Certificates: []tls.Certificate{cert}, - ClientAuth: tls.RequireAndVerifyClientCert, - ClientCAs: certpool, - } +func get_certificate(ctx iris.Context) *x509.Certificate { + var rel, _ = url.QueryUnescape(ctx.GetHeader("X-Ssl-cert")) + var block, _ = pem.Decode([]byte(rel)) + var cert, _ = x509.ParseCertificate(block.Bytes) - tlsconfig.Rand = rand.Reader - listener, err := tls.Listen("tcp", Config.Host + ":" + Config.Port, &tlsconfig) - - if err != nil { - log.Fatalf("Failed to listen: %s", err) - } - - err = ssh_init() - if err != nil { - panic(err) - } - - ssh_user_list() + return cert +} - app.Run(iris.Listener(listener), iris.WithConfiguration(iris.Configuration{ - FireMethodNotAllowed: true, - })) +func main() { + app := iris.New() + app.Use(func(ctx iris.Context) { + log.WithFields(log.Fields{ + "ip": ctx.RemoteAddr(), + "path": ctx.Path(), + "method": ctx.Method(), + }).Info("api request") + ctx.Next() + }) + + err := setConfig() + if err != nil { + panic(err) + } + + createSlaves(Config.Slaves) + + app.Delete("/api/user/key/{id:int32}", user_delete_key) + app.Get("/api/user/key/{id:int32}", user_get_key) + app.Get("/api/user/init", user_init) + app.Get("/api/user/inspect", user_inspect) + app.Post("/api/user/key", user_add_key) + app.Get("/api/instance/list", instance_list) + app.Get("/api/instance/exists/{name:string}", instance_exists) + app.Post("/api/instance/add", instance_add) + app.Get("/api/instance/{id:int32}", instance_get) + app.Get("/api/instance/{id:int32}/network", instance_network) + app.Post("/api/instance/{id:int32}/bind", instance_bind) + app.Delete("/api/instance/{id:int32}", instance_delete) + + err = ssh_init() + if err != nil { + panic(err) + } + + app.Run(iris.Addr(":8080"), iris.WithConfiguration(iris.Configuration{ + FireMethodNotAllowed: true, + })) } diff --git a/api/slaves.go b/api/slaves.go @@ -0,0 +1,322 @@ +package main + +import ( + "bytes" + "encoding/json" + "errors" + log "github.com/sirupsen/logrus" + "hash/fnv" + "io/ioutil" + "net/http" +) + +type Slave struct { + Address string + Name string + Arch string +} + +var Slaves []*Slave + +func httpRequest(verb string, slave *Slave, route string, body []byte) ([]byte, error) { + url := "http://" + slave.Address + "/" + route + req, err := http.NewRequest(verb, url, bytes.NewBuffer(body)) + if err != nil { + return nil, err + } + res, err := http.DefaultClient.Do(req) + if err != nil { + return nil, err + } + defer res.Body.Close() + resp, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, err + } + return resp, nil +} + +type SlaveInfo struct { + Name string `json:"name"` + Arch string `json:"arch"` +} + +func slaveGetInfo(slave *Slave) { + var info SlaveInfo + + resp, err := httpRequest("GET", slave, "api/info", nil) + if err != nil { + log.Errorf("Cannot get info from %s", slave.Address) + return + } + + json.Unmarshal(resp, &info) + slave.Name = info.Name + slave.Arch = info.Arch +} + +func getSlaveByName(name string) *Slave { + for _, slave := range Slaves { + if slave.Name == name { + return slave + } + } + + return nil +} + +func chooseSlave(instance *Instance) error { + var archSlaves []*Slave + + for _, slave := range Slaves { + if slave.Arch != instance.Architecture { + continue + } + + archSlaves = append(archSlaves, slave) + } + + if len(archSlaves) == 0 { + log.Errorf("Could not find any slave for %s", instance.Name) + return errors.New("Could not find any slave") + } + + log.Infof("Found %d slaves for instance %s", len(archSlaves), instance.Name) + + h := fnv.New32a() + h.Write([]byte(instance.Name)) + slaveId := h.Sum32() % uint32(len(archSlaves)) + log.Infof("Choosed slave %s for instance %s", archSlaves[slaveId], instance.Name) + + instance.PhysicalNode = archSlaves[slaveId].Name + + Database.ID(instance.ID).Cols("status").Update(&Instance{Status: InstanceAllocated}) + Database.ID(instance.ID).Cols("physical_node").Update(&Instance{PhysicalNode: archSlaves[slaveId].Name}) + return nil +} + +type InstanceAdd struct { + Distro string `json:"distro"` + Release string `json:"release"` + Name string `json:"name"` + Arch string `json:"arch"` +} + +func createInstance(instance *Instance) error { + slave := getSlaveByName(instance.PhysicalNode) + req := &InstanceAdd{ + Distro: instance.OS, + Release: instance.Release, + Name: instance.Name, + Arch: instance.Architecture, + } + + body, _ := json.Marshal(req) + _, err := httpRequest("POST", slave, "api/instance", body) + + if err != nil { + log.Errorf("Could not create instance") + return err + } + + Database.ID(instance.ID).Cols("status").Update(&Instance{Status: InstanceStopped}) + return nil +} + +func startInstance(instance *Instance) error { + slave := getSlaveByName(instance.PhysicalNode) + + _, err := httpRequest("PUT", slave, "api/instance/"+instance.Name+"/start", nil) + if err != nil { + log.Errorf("Could not start instance") + return err + } + + Database.ID(instance.ID).Cols("status").Update(&Instance{Status: InstanceStarted}) + return nil +} + +func stopInstance(instance *Instance) error { + slave := getSlaveByName(instance.PhysicalNode) + + _, err := httpRequest("PUT", slave, "api/instance/"+instance.Name+"/stop", nil) + if err != nil { + log.Errorf("Could not start instance") + return err + } + + Database.ID(instance.ID).Cols("status").Update(&Instance{Status: InstanceStopped}) + return nil +} + +func rebootInstance(instance *Instance) error { + slave := getSlaveByName(instance.PhysicalNode) + + instance.Status = InstanceStopped + Database.Update(&instance) + + _, err := httpRequest("PUT", slave, "api/instance/"+instance.Name+"/reboot", nil) + if err != nil { + log.Errorf("Could not start instance") + return err + } + + Database.ID(instance.ID).Cols("status").Update(&Instance{Status: InstanceStarted}) + return nil +} + +type InstanceGet struct { + Name string `json:"name"` + Status int `json:"status"` + IPv4 []string `json:"IPv4"` + IPv6 []string `json:"IPv6"` + Gateway string `json:"gateway"` +} + +func instanceGet(instance *Instance) error { + var infos InstanceGet + + slave := getSlaveByName(instance.PhysicalNode) + + resp, err := httpRequest("GET", slave, "api/instance/"+instance.Name, nil) + if err != nil { + log.Errorf("Could not get instance") + return err + } + json.Unmarshal(resp, &infos) + + if len(infos.IPv4) == 0 { + instance.IPv4 = "Unknown" + } else { + instance.IPv4 = infos.IPv4[0] + } + + if len(infos.IPv6) == 0 { + instance.IPv6 = "Unknown" + } else { + instance.IPv6 = infos.IPv6[0] + } + instance.Gateway = infos.Gateway + return nil +} + +func instanceDestroy(instance *Instance) error { + slave := getSlaveByName(instance.PhysicalNode) + + _, err := httpRequest("DELETE", slave, "api/instance/"+instance.Name, nil) + if err != nil { + log.Errorf("Could not delete instance") + return err + } + return nil +} + +type InstanceExec struct { + Cmd []string `json:"cmd"` +} + +func instanceInstallAnsible(instance *Instance) error { + slave := getSlaveByName(instance.PhysicalNode) + var exec InstanceExec + + Database.ID(instance.ID).Cols("status").Update(&Instance{Status: InstanceInstalling}) + if instance.OS == "ubuntu" || instance.OS == "ubuntu-core" { + exec = InstanceExec{ + Cmd: []string{ + "apt update -yy", + "apt install ansible git -yy", + }, + } + } else if instance.OS == "debian" { + exec = InstanceExec{ + Cmd: []string{ + "echo 'deb http://ftp.de.debian.org/debian stretch-backports main' >> /etc/apt/sources.list", + "apt update -yy", + "apt install -t stretch-backports ansible git -yy", + }, + } + } else if instance.OS == "archlinux" { + exec = InstanceExec{ + Cmd: []string{ + "pacman -Syu ansible git --noconfirm", + }, + } + } else if instance.OS == "fedora" || instance.OS == "centos" { + exec = InstanceExec{ + Cmd: []string{ + "dnf install -y ansible git", + "ln -sf /usr/bin/python3 /usr/bin/python", + }, + } + } else if instance.OS == "alpinelinux" { + exec = InstanceExec{ + Cmd: []string{ + "apk add -y ansible git", + }, + } + } else if instance.OS == "gentoo" { + + exec = InstanceExec{ + Cmd: []string{ + "USE='-ssl sqlite -cli' emerge app-admin/ansible dev-vcs/git app-portage/gentoolkit", + }, + } + } + + body, _ := json.Marshal(exec) + _, err := httpRequest("POST", slave, "api/instance/"+instance.Name+"/exec", body) + if err != nil { + return err + } + + exec = InstanceExec{ + Cmd: []string{ + "ansible-pull -U git://git.ne02ptzero.me/cisco-ansible init.yml", + }, + } + body, _ = json.Marshal(exec) + _, err = httpRequest("POST", slave, "api/instance/"+instance.Name+"/exec", body) + Database.ID(instance.ID).Cols("status").Update(&Instance{Status: InstanceStarted}) + return err +} + +type InstanceBind struct { + IP string `json:"ip"` + Port int `json:"port"` +} + +func instanceBindPort(instance *Instance, port string) (InstanceBind, error) { + slave := getSlaveByName(instance.PhysicalNode) + var instanceBind InstanceBind + + body, err := httpRequest("PUT", slave, "api/instance/"+instance.Name+"/bind/"+port, nil) + err = json.Unmarshal(body, &instanceBind) + return instanceBind, err +} + +func instanceBindLocalPort(instance *Instance, port string) (InstanceBind, error) { + slave := getSlaveByName(instance.PhysicalNode) + var instanceBind InstanceBind + + body, err := httpRequest("PUT", slave, "api/instance/"+instance.Name+"/bind_local/"+port, nil) + err = json.Unmarshal(body, &instanceBind) + return instanceBind, err +} + +func instanceGetNetwork(instance *Instance) ([]byte, error) { + slave := getSlaveByName(instance.PhysicalNode) + body, err := httpRequest("GET", slave, "api/instance/"+instance.Name+"/network", nil) + return body, err +} + +func createSlaves(slaves []string) { + for _, slave := range slaves { + s := &Slave{ + Address: slave, + } + Slaves = append(Slaves, s) + slaveGetInfo(s) + } + + log.Infof("%v", Slaves[1]) +} diff --git a/api/ssh.go b/api/ssh.go @@ -1,50 +1,169 @@ package main import ( - "io/ioutil" + "io/ioutil" - log "github.com/sirupsen/logrus" - "golang.org/x/crypto/ssh" + "encoding/json" + log "github.com/sirupsen/logrus" + "golang.org/x/crypto/ssh" + "io" + "strconv" ) +type Key struct { + ID int32 `json:"ID"` + Key string `json:"Key"` + UserID int `json:"UserID"` + Comment string `json:"Comment"` +} + +type User struct { + ID int `json:"ID"` + Email string `json:"Email"` + Name string `json:"Name"` + Keys []Key `json:"Keys"` +} + var client *ssh.Client -func ssh_user_list() error { - session, err := client.NewSession() - if err != nil { - return err - } +func ssh_exec_command(cmd string) (string, error) { + session, err := client.NewSession() + if err != nil { + log.Fatal(err) + return "", err + } + + defer session.Close() + + log.Infof("Executing command '%s' on sshportal", cmd) + out, err := session.CombinedOutput(cmd) + log.Infof("Output is '%s'", string(out)) + if err != nil { + log.Fatal(err) + return "", err + } + + return string(out), nil +} + +func ssh_user_list() (string, error) { + return ssh_exec_command("user ls") +} + +func ssh_user_inspect(name string) (string, error) { + return ssh_exec_command("user inspect " + name) +} + +func ssh_get_key_user(name string, id int32) (*Key, error) { + var users []User + + result, err := ssh_user_inspect(name) + if err != nil { + return nil, err + } + + err = json.Unmarshal([]byte(result), &users) + if err != nil { + return nil, err + } - defer session.Close() + for i := 0; i < len(users[0].Keys); i++ { + if id == users[0].Keys[i].ID { + return &users[0].Keys[i], nil + } + } - out, err := session.CombinedOutput("user ls") - if err != nil { - log.Fatal(err) - return err - } - log.Infof("%s", out) - return nil + return nil, nil +} + +func ssh_user_key_del(id int32) error { + _, err := ssh_exec_command("userkey rm " + strconv.FormatInt(int64(id), 10)) + return err +} + +func ssh_add_user_key(user string, key string) (string, error) { + session, err := client.NewSession() + if err != nil { + log.Fatal(err) + return "", err + } + + defer session.Close() + + log.Infof("Key: %s", key) + stdin, err := session.StdinPipe() + io.WriteString(stdin, key) + + log.Infof("Executing command '%s' on sshportal", "userkey create "+user) + out, err := session.CombinedOutput("userkey create " + user) + log.Infof("Output is '%s'", string(out)) + + if err != nil { + log.Fatal(err) + return "", err + } + return string(out), nil +} + +func ssh_add_host(name string, user string, address string) error { + _, err := ssh_exec_command("hostgroup create --name " + name) + if err != nil { + return err + } + _, err = ssh_exec_command("host create --name " + name + " --group " + name + " ssh://root@" + address) + if err != nil { + return err + } + _, err = ssh_exec_command("usergroup create --name " + name) + if err != nil { + return err + } + _, err = ssh_exec_command("user update " + user + " -g " + name) + if err != nil { + return err + } + _, err = ssh_exec_command("acl create --hg " + name + " --ug " + name) + if err != nil { + return err + } + return nil +} + +func ssh_rm_host(name string) error { + _, err := ssh_exec_command("hostgroup rm " + name) + if err != nil { + return err + } + _, err = ssh_exec_command("host rm " + name) + if err != nil { + return err + } + _, err = ssh_exec_command("usergroup rm " + name) + if err != nil { + return err + } + return nil } func ssh_init() error { - key, err := ioutil.ReadFile(Config.SSHKey) - if err != nil { - log.Fatalf("unable to read private key: %v", err) - } - - signer, err := ssh.ParsePrivateKey(key) - if err != nil { - log.Fatalf("unable to parse private key: %v", err) - } - - config := &ssh.ClientConfig{ - User: "admin", - Auth: []ssh.AuthMethod{ - ssh.PublicKeys(signer), - }, - HostKeyCallback: ssh.InsecureIgnoreHostKey(), - } - - client, err = ssh.Dial("tcp", Config.SSHProxy + ":22", config) - return err + key, err := ioutil.ReadFile(Config.SSHKey) + if err != nil { + log.Fatalf("unable to read private key: %v", err) + } + + signer, err := ssh.ParsePrivateKey(key) + if err != nil { + log.Fatalf("unable to parse private key: %v", err) + } + + config := &ssh.ClientConfig{ + User: "admin", + Auth: []ssh.AuthMethod{ + ssh.PublicKeys(signer), + }, + HostKeyCallback: ssh.InsecureIgnoreHostKey(), + } + + client, err = ssh.Dial("tcp", Config.SSHProxy+":22", config) + return err } diff --git a/api/user.go b/api/user.go @@ -1,15 +1,81 @@ package main import ( - "github.com/kataras/iris" + "github.com/kataras/iris" + "strings" ) -func user_keys(ctx iris.Context) { +func user_get_key(ctx iris.Context) { + var cert = get_certificate(ctx) + id, err := ctx.Params().GetInt32("id") + + result, err := ssh_get_key_user(cert.Subject.CommonName, id) + if err != nil { + return + } + + if result == nil { + ctx.StatusCode(iris.StatusUnauthorized) + return + } + + ctx.JSON(result) +} + +func user_delete_key(ctx iris.Context) { + var cert = get_certificate(ctx) + id, err := ctx.Params().GetInt32("id") + + result, err := ssh_get_key_user(cert.Subject.CommonName, id) + if err != nil { + return + } + + if result == nil { + ctx.StatusCode(iris.StatusUnauthorized) + return + } + + err = ssh_user_key_del(id) + ctx.ContentType("application/json") + if err != nil { + ctx.Writef("{status: \"OK\"}") + } else { + ctx.Writef("{status: \"KO\"}") + } } func user_init(ctx iris.Context) { } +type KeyPost struct { + Key string `json:"key"` +} + func user_add_key(ctx iris.Context) { + var key Key + var cert = get_certificate(ctx) + + if err := ctx.ReadJSON(&key); err != nil { + ctx.StatusCode(iris.StatusBadRequest) + return + } + + out, err := ssh_add_user_key(cert.Subject.CommonName, key.Key) + if err != nil { + ctx.StatusCode(iris.StatusInternalServerError) + } + + ctx.ContentType("application/json") + ctx.Writef("{\"status\": \"OK\", \"id\": %s}", strings.Split(out, "\n")[1]) +} + +func user_inspect(ctx iris.Context) { + var cert = get_certificate(ctx) + result, err := ssh_user_inspect(cert.Subject.CommonName) + if err == nil { + ctx.ContentType("application/json") + ctx.Writef(result) + } }