[TASK] add ssh manager, config and log
This commit is contained in:
parent
4c6cedb91b
commit
944489406c
|
@ -0,0 +1,131 @@
|
|||
## Core latex/pdflatex auxiliary files:
|
||||
*.aux
|
||||
*.lof
|
||||
*.log
|
||||
*.lot
|
||||
*.fls
|
||||
*.out
|
||||
*.toc
|
||||
|
||||
## Intermediate documents:
|
||||
*.dvi
|
||||
*-converted-to.*
|
||||
# these rules might exclude image files for figures etc.
|
||||
*.ps
|
||||
*.eps
|
||||
*.pdf
|
||||
|
||||
## Bibliography auxiliary files (bibtex/biblatex/biber):
|
||||
*.bbl
|
||||
*.bcf
|
||||
*.blg
|
||||
*-blx.aux
|
||||
*-blx.bib
|
||||
*.brf
|
||||
*.run.xml
|
||||
|
||||
## Build tool auxiliary files:
|
||||
*.fdb_latexmk
|
||||
*.synctex
|
||||
*.synctex.gz
|
||||
*.synctex.gz(busy)
|
||||
*.pdfsync
|
||||
|
||||
## Auxiliary and intermediate files from other packages:
|
||||
|
||||
# algorithms
|
||||
*.alg
|
||||
*.loa
|
||||
|
||||
# achemso
|
||||
acs-*.bib
|
||||
|
||||
# amsthm
|
||||
*.thm
|
||||
|
||||
# beamer
|
||||
*.nav
|
||||
*.snm
|
||||
*.vrb
|
||||
|
||||
#(e)ledmac/(e)ledpar
|
||||
*.end
|
||||
*.[1-9]
|
||||
*.[1-9][0-9]
|
||||
*.[1-9][0-9][0-9]
|
||||
*.[1-9]R
|
||||
*.[1-9][0-9]R
|
||||
*.[1-9][0-9][0-9]R
|
||||
*.eledsec[1-9]
|
||||
*.eledsec[1-9]R
|
||||
*.eledsec[1-9][0-9]
|
||||
*.eledsec[1-9][0-9]R
|
||||
*.eledsec[1-9][0-9][0-9]
|
||||
*.eledsec[1-9][0-9][0-9]R
|
||||
|
||||
# glossaries
|
||||
*.acn
|
||||
*.acr
|
||||
*.glg
|
||||
*.glo
|
||||
*.gls
|
||||
|
||||
# hyperref
|
||||
*.brf
|
||||
|
||||
# knitr
|
||||
*-concordance.tex
|
||||
*.tikz
|
||||
*-tikzDictionary
|
||||
|
||||
# listings
|
||||
*.lol
|
||||
|
||||
# makeidx
|
||||
*.idx
|
||||
*.ilg
|
||||
*.ind
|
||||
*.ist
|
||||
|
||||
# minitoc
|
||||
*.maf
|
||||
*.mtc
|
||||
*.mtc0
|
||||
|
||||
# minted
|
||||
_minted*
|
||||
*.pyg
|
||||
|
||||
# morewrites
|
||||
*.mw
|
||||
|
||||
# nomencl
|
||||
*.nlo
|
||||
|
||||
# sagetex
|
||||
*.sagetex.sage
|
||||
*.sagetex.py
|
||||
*.sagetex.scmd
|
||||
|
||||
# sympy
|
||||
*.sout
|
||||
*.sympy
|
||||
sympy-plots-for-*.tex/
|
||||
|
||||
# todonotes
|
||||
*.tdo
|
||||
|
||||
# xindy
|
||||
*.xdy
|
||||
|
||||
|
||||
__pycache__
|
||||
|
||||
# IDE's go
|
||||
.idea/
|
||||
|
||||
|
||||
# go project
|
||||
profile.cov
|
||||
config.conf
|
||||
cmd/stock/config.conf
|
|
@ -0,0 +1,26 @@
|
|||
#!/bin/bash
|
||||
# Issue: https://github.com/mattn/goveralls/issues/20
|
||||
# Source: https://github.com/uber/go-torch/blob/63da5d33a225c195fea84610e2456d5f722f3963/.test-cover.sh
|
||||
|
||||
echo "mode: count" > profile.cov
|
||||
FAIL=0
|
||||
|
||||
# Standard go tooling behavior is to ignore dirs with leading underscors
|
||||
for dir in $(find . -maxdepth 10 -not -path './.git*' -not -path '*/_*' -type d);
|
||||
do
|
||||
if ls $dir/*.go &> /dev/null; then
|
||||
go test -p 1 -v -covermode=count -coverprofile=profile.tmp $dir || FAIL=$?
|
||||
if [ -f profile.tmp ]
|
||||
then
|
||||
tail -n +2 < profile.tmp >> profile.cov
|
||||
rm profile.tmp
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Failures have incomplete results, so don't send
|
||||
if [ "$FAIL" -eq 0 ]; then
|
||||
goveralls -service=travis-ci -v -coverprofile=profile.cov
|
||||
fi
|
||||
|
||||
exit $FAIL
|
|
@ -0,0 +1,10 @@
|
|||
language: go
|
||||
go:
|
||||
- tip
|
||||
install:
|
||||
- go get -t github.com/FreifunkBremen/freifunkmanager/...
|
||||
- go get github.com/mattn/goveralls
|
||||
- go get "golang.org/x/tools/cmd/cover"
|
||||
script:
|
||||
- ./.test-coverage
|
||||
- go install github.com/FreifunkBremen/freifunkmanager/cmd/freifunkmanager
|
|
@ -0,0 +1,61 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/NYTimes/gziphandler"
|
||||
goji "goji.io"
|
||||
"goji.io/pat"
|
||||
|
||||
configPackage "github.com/FreifunkBremen/freifunkmanager/config"
|
||||
"github.com/FreifunkBremen/freifunkmanager/lib/log"
|
||||
"github.com/FreifunkBremen/freifunkmanager/ssh"
|
||||
)
|
||||
|
||||
var (
|
||||
configFile string
|
||||
config *configPackage.Config
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.StringVar(&configFile, "config", "config.conf", "path of configuration file (default:config.conf)")
|
||||
flag.Parse()
|
||||
|
||||
config = configPackage.ReadConfigFile(configFile)
|
||||
|
||||
log.Log.Info("starting...")
|
||||
|
||||
sshmanager := ssh.NewManager(config.SSHPrivateKey)
|
||||
|
||||
// Startwebserver
|
||||
router := goji.NewMux()
|
||||
|
||||
router.Handle(pat.New("/*"), gziphandler.GzipHandler(http.FileServer(http.Dir(config.Webroot))))
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: config.WebserverBind,
|
||||
Handler: router,
|
||||
}
|
||||
go func() {
|
||||
if err := srv.ListenAndServe(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
log.Log.Info("started")
|
||||
|
||||
// Wait for system signal
|
||||
sigs := make(chan os.Signal, 1)
|
||||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
||||
sig := <-sigs
|
||||
|
||||
// Stop services
|
||||
srv.Close()
|
||||
sshmanager.Close()
|
||||
|
||||
log.Log.Info("stop recieve:", sig)
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
|
||||
"github.com/FreifunkBremen/freifunkmanager/lib/log"
|
||||
)
|
||||
|
||||
//config file of this daemon (for more the config_example.conf in git repository)
|
||||
type Config struct {
|
||||
// address on which the api and static content webserver runs
|
||||
WebserverBind string `toml:"webserver_bind"`
|
||||
|
||||
// path to deliver static content
|
||||
Webroot string `toml:"webroot"`
|
||||
// yanic socket
|
||||
YanicSocket string `toml:"yanic_socket"`
|
||||
|
||||
// SSH private key
|
||||
SSHPrivateKey string `toml:"ssh_key"`
|
||||
}
|
||||
|
||||
//reads a config model from path of a yml file
|
||||
func ReadConfigFile(path string) *Config {
|
||||
config := &Config{}
|
||||
file, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
log.Log.Panic(err)
|
||||
}
|
||||
|
||||
if err := toml.Unmarshal(file, config); err != nil {
|
||||
log.Log.Panic(err)
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestReadConfig(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
config := ReadConfigFile("../config_example.conf")
|
||||
assert.NotNil(config)
|
||||
|
||||
assert.Equal(":8080", config.WebserverBind)
|
||||
|
||||
assert.Panics(func() {
|
||||
ReadConfigFile("../config_example.co")
|
||||
}, "wrong file")
|
||||
|
||||
assert.Panics(func() {
|
||||
ReadConfigFile("testdata/config_panic.conf")
|
||||
}, "wrong toml")
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
not unmarshalable
|
|
@ -0,0 +1,4 @@
|
|||
webserver_bind = ":8080"
|
||||
webroot = "webroot"
|
||||
yanic_socket = ""
|
||||
ssh_key = "/etc/a"
|
|
@ -0,0 +1,33 @@
|
|||
// Package log provides the
|
||||
// functionality to start und initialize to logger
|
||||
package log
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
logger "github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
// current logger with configuration
|
||||
var Log *logger.Logger
|
||||
|
||||
// Function to initiate a new logger
|
||||
func init() {
|
||||
Log = logger.New()
|
||||
log.SetOutput(Log.Writer()) // Enable fallback if core logger
|
||||
}
|
||||
|
||||
// Function to add the information of a http request to the log
|
||||
// Input: pointer to the http request r
|
||||
func HTTP(r *http.Request) *logger.Entry {
|
||||
ip := r.Header.Get("X-Forwarded-For")
|
||||
if len(ip) <= 1 {
|
||||
ip = r.RemoteAddr
|
||||
}
|
||||
return Log.WithFields(logger.Fields{
|
||||
"remote": ip,
|
||||
"method": r.Method,
|
||||
"url": r.URL.RequestURI(),
|
||||
})
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
// Package log provides the
|
||||
// functionality to start und initialize to logger
|
||||
package log
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// Function to test the logging
|
||||
// Input: pointer to teh testing object
|
||||
func TestLog(t *testing.T) {
|
||||
assertion := assert.New(t)
|
||||
|
||||
req, _ := http.NewRequest("GET", "https://google.com/lola/duda?q=wasd", nil)
|
||||
log := HTTP(req)
|
||||
_, ok := log.Data["remote"]
|
||||
|
||||
assertion.NotNil(ok, "remote address not set in logger")
|
||||
assertion.Equal("GET", log.Data["method"], "method not set in logger")
|
||||
assertion.Equal("/lola/duda?q=wasd", log.Data["url"], "path not set in logger")
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package ssh
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/agent"
|
||||
)
|
||||
|
||||
func SSHAgent() ssh.AuthMethod {
|
||||
if sshAgent, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil {
|
||||
return ssh.PublicKeysCallback(agent.NewClient(sshAgent).Signers)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func PublicKeyFile(file string) ssh.AuthMethod {
|
||||
buffer, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
key, err := ssh.ParsePrivateKey(buffer)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return ssh.PublicKeys(key)
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package ssh
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
||||
"github.com/FreifunkBremen/freifunkmanager/lib/log"
|
||||
)
|
||||
|
||||
func (m *Manager) ExecuteEverywhere(cmd string) {
|
||||
for host, client := range m.clients {
|
||||
m.execute(host, client, cmd)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) ExecuteOn(host net.IP, cmd string) {
|
||||
client := m.ConnectTo(host)
|
||||
m.execute(host.String(), client, cmd)
|
||||
}
|
||||
|
||||
func (m *Manager) execute(host string, client *ssh.Client, cmd string) {
|
||||
session, err := client.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
if err != nil {
|
||||
log.Log.Warnf("can not create session on %s: %s", host, err)
|
||||
delete(m.clients, host)
|
||||
return
|
||||
}
|
||||
err = session.Run(cmd)
|
||||
if err != nil {
|
||||
log.Log.Warnf("could not run %s on %s: %s", cmd, host, err)
|
||||
delete(m.clients, host)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package ssh
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestExecute(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
mgmt := NewManager("~/.ssh/id_rsa")
|
||||
assert.NotNil(mgmt, "no new manager created")
|
||||
|
||||
mgmt.ConnectTo(net.ParseIP("2a06:8782:ffbb:1337::127"))
|
||||
|
||||
mgmt.ExecuteEverywhere("echo $HOSTNAME")
|
||||
mgmt.ExecuteOn(net.ParseIP("2a06:8782:ffbb:1337::127"), "uptime")
|
||||
mgmt.ExecuteOn(net.ParseIP("2a06:8782:ffbb:1337::127"), "echo $HOSTNAME")
|
||||
|
||||
mgmt.Close()
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
package ssh
|
||||
|
||||
import (
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
||||
"github.com/FreifunkBremen/freifunkmanager/lib/log"
|
||||
)
|
||||
|
||||
// the SSH Connection Manager for multiple connections
|
||||
type Manager struct {
|
||||
config *ssh.ClientConfig
|
||||
clients map[string]*ssh.Client
|
||||
clientsMUX sync.Mutex
|
||||
}
|
||||
|
||||
// create a new SSH Connection Manager by ssh file
|
||||
func NewManager(file string) *Manager {
|
||||
var auths []ssh.AuthMethod
|
||||
if auth := SSHAgent(); auth != nil {
|
||||
auths = append(auths, auth)
|
||||
}
|
||||
if auth := PublicKeyFile(file); auth != nil {
|
||||
auths = append(auths, auth)
|
||||
}
|
||||
|
||||
sshConfig := &ssh.ClientConfig{
|
||||
User: "root",
|
||||
Auth: auths,
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
}
|
||||
return &Manager{
|
||||
config: sshConfig,
|
||||
clients: make(map[string]*ssh.Client),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) ConnectTo(host net.IP) *ssh.Client {
|
||||
m.clientsMUX.Lock()
|
||||
defer m.clientsMUX.Unlock()
|
||||
|
||||
if client, ok := m.clients[host.String()]; ok {
|
||||
return client
|
||||
}
|
||||
addr := net.TCPAddr{
|
||||
IP: host,
|
||||
Port: 22,
|
||||
}
|
||||
client, err := ssh.Dial("tcp", addr.String(), m.config)
|
||||
if err != nil {
|
||||
log.Log.Error(err)
|
||||
return nil
|
||||
}
|
||||
|
||||
m.clients[host.String()] = client
|
||||
return client
|
||||
}
|
||||
|
||||
func (m *Manager) Close() {
|
||||
for host, client := range m.clients {
|
||||
client.Close()
|
||||
delete(m.clients, host)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package ssh
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestManager(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
mgmt := NewManager("~/.ssh/id_rsa")
|
||||
assert.NotNil(mgmt, "no new manager created")
|
||||
|
||||
mgmt.ConnectTo(net.ParseIP("2a06:8782:ffbb:1337::127"))
|
||||
|
||||
mgmt.Close()
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
package ssh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
||||
"github.com/FreifunkBremen/freifunkmanager/lib/log"
|
||||
)
|
||||
|
||||
type SSHRunResultHandler func([]byte, error)
|
||||
|
||||
func (m *Manager) RunEverywhere(cmd string, handler SSHRunResultHandler) {
|
||||
for host, client := range m.clients {
|
||||
result, err := m.run(host, client, cmd)
|
||||
handler(result, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) RunOn(host net.IP, cmd string) ([]byte, error) {
|
||||
client := m.ConnectTo(host)
|
||||
return m.run(host.String(), client, cmd)
|
||||
}
|
||||
|
||||
func (m *Manager) run(host string, client *ssh.Client, cmd string) ([]byte, error) {
|
||||
session, err := client.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
if err != nil {
|
||||
log.Log.Warnf("can not create session on %s: %s", host, err)
|
||||
delete(m.clients, host)
|
||||
return nil, err
|
||||
}
|
||||
stdout, err := session.StdoutPipe()
|
||||
buffer := &bytes.Buffer{}
|
||||
go io.Copy(buffer, stdout)
|
||||
if err != nil {
|
||||
log.Log.Warnf("can not create pipe for run on %s: %s", host, err)
|
||||
delete(m.clients, host)
|
||||
return nil, err
|
||||
}
|
||||
err = session.Run(cmd)
|
||||
if err != nil {
|
||||
log.Log.Warnf("could not run %s on %s: %s", cmd, host, err)
|
||||
delete(m.clients, host)
|
||||
return nil, err
|
||||
}
|
||||
var result []byte
|
||||
for {
|
||||
b, err := buffer.ReadByte()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
result = append(result, b)
|
||||
}
|
||||
return result, nil
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package ssh
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRun(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
mgmt := NewManager("~/.ssh/id_rsa")
|
||||
assert.NotNil(mgmt, "no new manager created")
|
||||
|
||||
mgmt.ConnectTo(net.ParseIP("2a06:8782:ffbb:1337::127"))
|
||||
|
||||
mgmt.RunEverywhere("echo 13", func(result []byte, err error) {
|
||||
assert.NoError(err)
|
||||
|
||||
result = result[:len(result)-1]
|
||||
|
||||
assert.Equal([]byte{'1', '3'}, result)
|
||||
})
|
||||
result, err := mgmt.RunOn(net.ParseIP("2a06:8782:ffbb:1337::127"), "echo 16")
|
||||
assert.NoError(err)
|
||||
|
||||
result = result[:len(result)-1]
|
||||
resultInt, _ := strconv.Atoi(string(result))
|
||||
|
||||
assert.Equal(16, resultInt)
|
||||
|
||||
mgmt.Close()
|
||||
}
|
Loading…
Reference in New Issue