1
0
mirror of https://github.com/evilsocket/shieldwall.git synced 2021-09-07 00:28:37 +03:00

first commit

This commit is contained in:
Simone Margaritelli
2021-02-09 02:44:28 +01:00
commit 72774daf3c
34 changed files with 1507 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
_build
.DS_Store
.idea
dist
agent.yaml
api.yaml

23
Dockerfile Normal file
View File

@@ -0,0 +1,23 @@
FROM golang:alpine as builder
RUN apk update && apk add --no-cache make
# download, cache and install deps
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
# copy and compiled the app
COPY . .
RUN make clean
RUN make api
# start a new stage from scratch
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/_build/shieldwall-api .
EXPOSE 8666
ENTRYPOINT ["./shieldwall-api", "-debug"]

22
Makefile Normal file
View File

@@ -0,0 +1,22 @@
all: agent api
agent: _build
@go build -ldflags="-w -s" -o _build/shieldwall-agent cmd/agent/*.go
api: _build
@go build -ldflags="-w -s" -o _build/shieldwall-api cmd/api/*.go
test:
@go test -short ./...
_build:
@mkdir -p _build
clean:
@rm -rf _build
composer_build:
docker-compose build
composer_up: composer_build
docker-compose up

44
api/agent_get_rules.go Normal file
View File

@@ -0,0 +1,44 @@
package api
import (
"github.com/evilsocket/islazy/log"
"github.com/evilsocket/shieldwall/database"
"net/http"
"time"
)
func (api *API) GetRules(w http.ResponseWriter, r *http.Request) {
agentIP := clientIP(r)
agentToken := r.Header.Get("X-ShieldWall-Agent-Token")
agentUA := r.Header.Get("User-Agent")
if agentToken == "" {
log.Warning("[%s %s] received rules request with no token", agentIP, agentUA)
JSON(w, http.StatusBadRequest, nil)
return
}
// TODO: add cache with ttl
agent, err := database.FindAgentByToken(agentToken)
if err != nil {
log.Warning("[%s %s] error searching for token '%s': %v", agentIP, agentUA, agentToken, err)
JSON(w, http.StatusBadRequest, nil)
return
} else if agent == nil {
log.Warning("[%s %s] invalid token '%s'", agentIP, agentUA, agentToken)
JSON(w, http.StatusUnauthorized, nil)
return
}
log.Debug("[%s %s] successfully authenticated, returning %d rules", agentIP, agentUA, len(agent.Rules))
agent.UpdatedAt = time.Now()
agent.Address = agentIP
agent.UserAgent = agentUA
if err = agent.Save(); err != nil {
log.Error("error updating agent: %v", err)
}
JSON(w, http.StatusOK, agent.Rules)
}

16
api/config.go Normal file
View File

@@ -0,0 +1,16 @@
package api
import "github.com/evilsocket/shieldwall/mailer"
type EmailConfig struct {
From string `yaml:"from"`
SMTP mailer.Config `yaml:"smtp"`
}
type Config struct {
URL string `yaml:"url"`
Address string `yaml:"address"`
ReqMaxSize int64 `yaml:"req_max_size"`
TokenTTL int64 `yaml:"token_ttl"`
Secret string `yaml:"secret"`
}

59
api/setup.go Normal file
View File

@@ -0,0 +1,59 @@
package api
import (
"compress/flate"
"github.com/evilsocket/shieldwall/mailer"
"net/http"
"github.com/evilsocket/islazy/log"
"github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
"github.com/go-chi/cors"
)
type API struct {
config Config
mail EmailConfig
sendmail *mailer.Mailer
router *chi.Mux
}
func Setup(config Config, email EmailConfig, sendmail *mailer.Mailer) *API {
api := &API{
config: config,
mail: email,
sendmail: sendmail,
router: chi.NewRouter(),
}
compressor := middleware.NewCompressor(flate.DefaultCompression)
api.router.Use(compressor.Handler)
api.router.Use(cors.Handler(cors.Options{
AllowedOrigins: []string{"*"},
AllowedMethods: []string{"GET", "POST", "OPTIONS"},
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
AllowCredentials: false,
MaxAge: 300,
}))
api.router.Route("/api", func(r chi.Router) {
r.Route("/v1", func(r chi.Router) {
r.Get("/rules", api.GetRules)
r.Route("/user", func(r chi.Router) {
r.Post("/register", api.UserRegister)
r.Get("/verify/{verification:[A-Fa-f0-9]{64}}", api.UserVerify)
r.Post("/login", api.UserLogin)
})
})
})
return api
}
func (api *API) Run() {
log.Info("api starting on %s", api.config.Address)
log.Fatal("%v", http.ListenAndServe(api.config.Address, api.router))
}

67
api/user_login.go Normal file
View File

@@ -0,0 +1,67 @@
package api
import (
"encoding/json"
"github.com/dgrijalva/jwt-go"
"github.com/evilsocket/islazy/log"
"github.com/evilsocket/shieldwall/database"
"io"
"net/http"
"time"
)
type UserLoginRequest struct {
Email string `json:"email"`
Password string `json:"password"`
}
func (api *API) tokenFor(user *database.User) (string, error) {
claims := jwt.MapClaims{}
claims["authorized"] = true
claims["user_id"] = user.ID
claims["expires_at"] = time.Now().Add(time.Duration(api.config.TokenTTL) * time.Second).Format(time.RFC3339)
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
signed, err := token.SignedString([]byte(api.config.Secret))
if err != nil {
return "", err
}
return signed, nil
}
func (api *API) UserLogin(w http.ResponseWriter, r *http.Request) {
var req UserLoginRequest
defer r.Body.Close()
client := clientIP(r)
reader := io.LimitReader(r.Body, api.config.ReqMaxSize)
decoder := json.NewDecoder(reader)
err := decoder.Decode(&req)
if err != nil {
log.Warning("[%s] error decoding user login request: %v", client, err)
JSON(w, http.StatusBadRequest, nil)
return
}
user, err := database.LoginUser(client, req.Email, req.Password)
if err != nil {
ERROR(w, http.StatusUnauthorized, err)
return
} else if user == nil {
JSON(w, http.StatusUnauthorized, "invalid credentials")
return
} else if token, err := api.tokenFor(user); err != nil {
log.Error("error creating token for user %d: %v", user.ID, err)
ERROR(w, http.StatusInternalServerError, err)
return
} else {
log.Debug("[%s] user %s logged in", client, user.Email)
JSON(w, http.StatusOK, struct {
Token string `json:"token"`
}{
Token: token,
})
}
}

56
api/user_register.go Normal file
View File

@@ -0,0 +1,56 @@
package api
import (
"encoding/json"
"fmt"
"github.com/evilsocket/islazy/log"
"github.com/evilsocket/shieldwall/database"
"io"
"net/http"
)
type UserRegisterRequest struct {
Email string `json:"email"`
Password string `json:"password"`
}
func (api *API) UserRegister(w http.ResponseWriter, r *http.Request) {
var req UserRegisterRequest
defer r.Body.Close()
client := clientIP(r)
reader := io.LimitReader(r.Body, api.config.ReqMaxSize)
decoder := json.NewDecoder(reader)
err := decoder.Decode(&req)
if err != nil {
log.Warning("[%s] error decoding user registration request: %v", client, err)
JSON(w, http.StatusBadRequest, nil)
return
}
user, err := database.RegisterUser(client, req.Email, req.Password)
if err != nil {
log.Warning("[%s] %v", client, err)
ERROR(w, http.StatusBadRequest, err)
return
}
log.Info("[%s] registered new user %s", user.Address, user.Email)
// prepare and send verification email
link := fmt.Sprintf("%s/api/v1/user/verify/%s", api.config.URL, user.Verification)
emailSubject := "shieldwall.me account verification"
emailBody := "Follow this link to complete your registration.<br/><br/>" +
fmt.Sprintf("<a href=\"%s\">%s</a>", link, link)
if err = api.sendmail.Send(api.mail.From, user.Email, emailSubject, emailBody); err != nil {
log.Error("error sending verification email to %s: %v", user.Email, err)
} else {
log.Debug("verification email sent to %s (%s)", user.Email, user.Verification)
}
JSON(w, http.StatusOK, "registration successful, proceed to email verification")
}

22
api/user_verify.go Normal file
View File

@@ -0,0 +1,22 @@
package api
import (
"github.com/evilsocket/shieldwall/database"
"github.com/go-chi/chi"
"net/http"
)
func (api *API) UserVerify(w http.ResponseWriter, r *http.Request) {
code := chi.URLParam(r, "verification")
if code == "" {
JSON(w, http.StatusBadRequest, nil)
return
}
if err := database.VerifyUser(code); err != nil {
JSON(w, http.StatusBadRequest, err)
return
}
JSON(w, http.StatusOK, "user successfully verified")
}

124
api/utils.go Normal file
View File

@@ -0,0 +1,124 @@
package api
import (
"encoding/json"
"errors"
"net/http"
"strconv"
"strings"
"github.com/evilsocket/islazy/log"
)
var (
ErrEmpty = errors.New("")
ErrUnauthorized = errors.New("unauthorized")
ErrMessageNotFound = errors.New("record not found")
)
func strParam(r *http.Request, name string, def string) string {
param := r.URL.Query().Get(name)
if param != "" {
return param
}
return def
}
func floatParam(r *http.Request, name string, def float32) float32 {
param := r.URL.Query().Get(name)
if param != "" {
if v, err := strconv.ParseFloat(param, 32); err != nil {
log.Warning("can't parse %s parameter from value '%s'", name, param)
} else {
return float32(v)
}
}
return def
}
func boolParam(r *http.Request, name string, def bool) bool {
param := r.URL.Query().Get(name)
if param != "" {
if v, err := strconv.ParseBool(param); err != nil {
log.Warning("can't parse %s parameter from value '%s'", name, param)
} else {
return v
}
}
return def
}
func intParam(r *http.Request, name string, def int) int {
param := r.URL.Query().Get(name)
if param != "" {
if v, err := strconv.ParseInt(param, 10, 32); err != nil {
log.Warning("can't parse %s parameter from value '%s'", name, param)
} else {
return int(v)
}
}
return def
}
func clientIP(r *http.Request) string {
address := strings.Split(r.RemoteAddr, ":")[0]
if forwardedFor := r.Header.Get("X-Forwarded-For"); forwardedFor != "" {
address = forwardedFor
}
// https://support.cloudflare.com/hc/en-us/articles/206776727-What-is-True-Client-IP-
if trueClient := r.Header.Get("True-Client-IP"); trueClient != "" {
address = trueClient
}
// handle multiple IPs case
return strings.Trim(strings.Split(address, ",")[0], " ")
}
func reqToken(r *http.Request) string {
keys := r.URL.Query()
token := keys.Get("token")
if token != "" {
return token
}
bearerToken := r.Header.Get("Authorization")
if parts := strings.Split(bearerToken, " "); len(parts) == 2 {
return parts[1]
}
return ""
}
func pageNum(r *http.Request) (int, error) {
pageParam := r.URL.Query().Get("p")
if pageParam == "" {
pageParam = "1"
}
return strconv.Atoi(pageParam)
}
func JSON(w http.ResponseWriter, statusCode int, data interface{}) {
js, err := json.Marshal(data)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(statusCode)
if _, err = w.Write(js); err != nil {
log.Error("error sending response: %v", err)
} else {
// log.Debug("sent %d bytes of json response", sent)
}
}
func ERROR(w http.ResponseWriter, statusCode int, err error) {
if err != nil {
JSON(w, statusCode, struct {
Error string `json:"error"`
}{
Error: err.Error(),
})
return
}
JSON(w, http.StatusBadRequest, nil)
}

64
cmd/agent/api.go Normal file
View File

@@ -0,0 +1,64 @@
package main
import (
"encoding/json"
"fmt"
"github.com/evilsocket/islazy/log"
"github.com/evilsocket/shieldwall/firewall"
"github.com/evilsocket/shieldwall/version"
"net/http"
"runtime"
"strings"
"time"
)
// API client
type API struct {
Server string
Token string
Timeout int
}
func (a API) FetchRules() ([]firewall.Rule, error) {
client := &http.Client{}
if a.Timeout > 0 {
client.Timeout = time.Duration(a.Timeout) * time.Second
}
if strings.Index(a.Server, "://") == -1 {
a.Server = "https://" + a.Server
}
url := fmt.Sprintf("%s/api/v1/rules", a.Server)
log.Debug("polling %s", url)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
// agent authentication
req.Header.Set("X-ShieldWall-Agent-Token", a.Token)
req.Header.Set("User-Agent", fmt.Sprintf(
"ShieldWall Agent v%s (%s %s)",
version.Version,
runtime.GOOS,
runtime.GOARCH))
res, err := client.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return nil, fmt.Errorf("%d (%s)", res.StatusCode, res.Status)
}
var rules []firewall.Rule
if err = json.NewDecoder(res.Body).Decode(&rules); err != nil {
return nil, err
}
return rules, nil
}

39
cmd/agent/config.go Normal file
View File

@@ -0,0 +1,39 @@
package main
import (
"gopkg.in/yaml.v2"
"io/ioutil"
)
type Config struct {
// TODO: refactor to support different firewalls
IPTablesPath string `yaml:"iptables"`
// api server hostname
Server string `yaml:"server"`
// auth token
Token string `yaml:"token"`
// api polling period
Period int `yaml:"period"`
// api timeout
Timeout int `yaml:"timeout"`
// where lists are stored
DataPath string `yaml:"data"`
// list of addresses to pre allow
Allow []string `yaml:"allow"`
}
func LoadAgentConfig(fileName string) (*Config, error) {
raw, err := ioutil.ReadFile(fileName)
if err != nil {
return nil, err
}
conf := Config{}
err = yaml.Unmarshal(raw, &conf)
if err != nil {
return nil, err
}
return &conf, nil
}

108
cmd/agent/main.go Normal file
View File

@@ -0,0 +1,108 @@
package main
import (
"flag"
"github.com/evilsocket/islazy/log"
"github.com/evilsocket/shieldwall/firewall"
"github.com/evilsocket/shieldwall/version"
"os"
"os/signal"
"syscall"
"time"
)
var (
err = (error)(nil)
conf = (* Config)(nil)
state = (*State)(nil)
signals = make(chan os.Signal, 1)
)
func signalHandler() {
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)
s := <-signals
log.Raw("\n")
log.Warning("RECEIVED SIGNAL: %s", s)
os.Exit(1)
}
func addAllowRules(s *State) {
for _, address := range conf.Allow {
state.Rules = append(state.Rules, firewall.Rule{
Type: firewall.RuleAllow,
Address: address,
Protocol: firewall.ProtoAll,
Ports: []string{firewall.AllPorts},
})
}
}
func main() {
flag.Parse()
setupLogging()
go signalHandler()
log.Info("shieldwall agent v%s", version.Version)
// load configuration
if conf, err = LoadAgentConfig(confFile); err != nil {
log.Fatal("error reading %s: %v", confFile, err)
}
// initialize firewall
if err = firewall.SetPath(conf.IPTablesPath); err != nil {
log.Fatal("%v", err)
}
// load saved state and run rules
if state, err = LoadState(conf.DataPath); err != nil {
log.Fatal("%v", err)
}
// new state, add the entries allowed by configuration
if len(state.Rules) == 0 && len(conf.Allow) > 0 {
addAllowRules(state)
}
// apply previous rules from the saved state
if err = firewall.Apply(state.Rules); err != nil {
log.Fatal("%v", err)
}
// TODO: split in APIConfig
api := API{
Server: conf.Server,
Token: conf.Token,
Timeout: conf.Timeout,
}
// main loop
for {
if rules, err := api.FetchRules(); err != nil {
log.Error("error polling api: %v", err)
} else {
state.Rules = rules
log.Debug("got rules baby %#v", state.Rules)
log.Info("applying %d rules", len(state.Rules))
if len(conf.Allow) > 0 {
addAllowRules(state)
}
if err = firewall.Apply(state.Rules); err != nil {
log.Fatal("%v", err)
}
}
if err = state.Save(conf.DataPath); err != nil {
log.Error("error saving state to %s: %v", conf.DataPath, err)
} else {
log.Debug("state saved to %s", conf.DataPath)
}
time.Sleep(time.Second * time.Duration(conf.Period))
}
}

40
cmd/agent/setup.go Normal file
View File

@@ -0,0 +1,40 @@
package main
import (
"flag"
"github.com/evilsocket/islazy/log"
)
var (
confFile = ""
debug = false
logfile = ""
)
func init() {
flag.StringVar(&confFile, "config", "/etc/shieldwall/config.yaml", "YAML configuration file.")
flag.BoolVar(&debug, "debug", debug, "Enable debug logs.")
flag.StringVar(&logfile, "log", logfile, "Log messages to this file instead of standard error.")
}
func setupLogging() {
if logfile != "" {
log.Output = logfile
}
if debug == true {
log.Level = log.DEBUG
} else {
log.Level = log.INFO
}
log.DateFormat = "06-Jan-02"
log.TimeFormat = "15:04:05"
log.DateTimeFormat = "2006-01-02 15:04:05"
log.Format = "{datetime} {level:color}{level:name}{reset} {message}"
if err := log.Open(); err != nil {
panic(err)
}
}

61
cmd/agent/state.go Normal file
View File

@@ -0,0 +1,61 @@
package main
import (
"encoding/json"
"fmt"
"github.com/evilsocket/islazy/fs"
"github.com/evilsocket/islazy/log"
"github.com/evilsocket/shieldwall/firewall"
"io/ioutil"
"os"
"path/filepath"
"time"
)
const stateFileName = "state.json"
type State struct {
UpdatedAt time.Time `json:"updated_at"`
Rules []firewall.Rule `json:"rules"`
}
func LoadState(path string) (*State, error) {
if !fs.Exists(path) {
log.Info("creating %s", path)
if err := os.MkdirAll(path, os.ModePerm); err != nil {
return nil, err
}
}
fileName := filepath.Join(path, stateFileName)
if !fs.Exists(fileName) {
log.Debug("%s doesn't exist, returning initial state", fileName)
return &State{
UpdatedAt: time.Now(),
}, nil
}
log.Debug("loading state from %s ...", fileName)
var state State
raw, err := ioutil.ReadFile(fileName)
if err != nil {
return nil, fmt.Errorf("error reading %s: %v", fileName, err)
}
if err = json.Unmarshal(raw, &state); err != nil {
return nil, fmt.Errorf("error decoding %s: %v", fileName, err)
}
return &state, nil
}
func (s *State) Save(path string) error {
s.UpdatedAt = time.Now()
raw, err := json.MarshalIndent(s, "", " ")
if err != nil {
return err
}
return ioutil.WriteFile(filepath.Join(path, stateFileName), raw, os.ModePerm)
}

23
cmd/api/config.go Normal file
View File

@@ -0,0 +1,23 @@
package main
import (
"github.com/evilsocket/shieldwall/api"
"github.com/evilsocket/shieldwall/database"
"github.com/ilyakaznacheev/cleanenv"
)
type Config struct {
Api api.Config `yaml:"api"`
Email api.EmailConfig `yaml:"mail"`
Database database.Config `yaml:"database"`
}
func LoadConfig(path string) (*Config, error) {
config := Config{}
if err := cleanenv.ReadConfig(path, &config); err != nil {
return nil, err
}
return &config, nil
}

40
cmd/api/main.go Normal file
View File

@@ -0,0 +1,40 @@
package main
import (
"flag"
"github.com/evilsocket/islazy/log"
"github.com/evilsocket/shieldwall/api"
"github.com/evilsocket/shieldwall/database"
"github.com/evilsocket/shieldwall/mailer"
"github.com/evilsocket/shieldwall/version"
)
var (
err = (error)(nil)
conf = (* Config)(nil)
mail = (*mailer.Mailer)(nil)
)
func main() {
flag.Parse()
setupLogging()
log.Info("shieldwall api v%s", version.Version)
if conf, err = LoadConfig(confFile); err != nil {
log.Fatal("error reading %s: %v", confFile, err)
}
if mail, err = mailer.New(conf.Email.SMTP); err != nil {
log.Fatal("error creating mailer: %v", err)
}
if err = database.Setup(conf.Database); err != nil {
log.Fatal("error connecting to database: %v", err)
}
server := api.Setup(conf.Api, conf.Email, mail)
server.Run()
}

40
cmd/api/setup.go Normal file
View File

@@ -0,0 +1,40 @@
package main
import (
"flag"
"github.com/evilsocket/islazy/log"
)
var (
confFile = ""
debug = false
logfile = ""
)
func init() {
flag.StringVar(&confFile, "config", "/etc/shieldwall/config.yaml", "YAML configuration file.")
flag.BoolVar(&debug, "debug", debug, "Enable debug logs.")
flag.StringVar(&logfile, "log", logfile, "Log messages to this file instead of standard error.")
}
func setupLogging() {
if logfile != "" {
log.Output = logfile
}
if debug == true {
log.Level = log.DEBUG
} else {
log.Level = log.INFO
}
log.DateFormat = "06-Jan-02"
log.TimeFormat = "15:04:05"
log.DateTimeFormat = "2006-01-02 15:04:05"
log.Format = "{datetime} {level:color}{level:name}{reset} {message}"
if err := log.Open(); err != nil {
panic(err)
}
}

3
database.env Normal file
View File

@@ -0,0 +1,3 @@
POSTGRES_USER=shieldwall
POSTGRES_PASSWORD=shieldwall
POSTGRES_DB=shieldwall

36
database/agent.go Normal file
View File

@@ -0,0 +1,36 @@
package database
import (
"errors"
"gorm.io/datatypes"
"gorm.io/gorm"
"time"
)
type Agent struct {
ID uint `gorm:"primarykey"`
CreatedAt time.Time `gorm:"index"`
UpdatedAt time.Time `gorm:"index"`
DeletedAt gorm.DeletedAt `gorm:"index"`
Name string
UserID uint `gorm:"index"`
Rules datatypes.JSON `sql:"type:jsonb"`
Token string `gorm:"index"`
Address string
UserAgent string
}
func FindAgentByToken(token string) (*Agent, error) {
var found Agent
if err := db.Where("token=?", token).First(&found).Error; errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil
} else if err == nil {
return &found, nil
} else {
return nil, err
}
}
func (a *Agent) Save() error {
return db.Save(a).Error
}

9
database/config.go Normal file
View File

@@ -0,0 +1,9 @@
package database
type Config struct {
Hostname string `yaml:"host"`
Port int `yaml:"port"`
User string `yaml:"user"`
Password string `yaml:"password"`
Name string `yaml:"name"`
}

26
database/jsonb.go Normal file
View File

@@ -0,0 +1,26 @@
package database
import (
"database/sql/driver"
"encoding/json"
"gorm.io/datatypes"
)
type JSONB map[string]interface{}
func (j JSONB) Value() (driver.Value, error) {
valueString, err := json.Marshal(j)
return string(valueString), err
}
func (j *JSONB) Scan(value interface{}) error {
if err := json.Unmarshal([]byte(value.(string)), &j); err != nil {
return err
}
return nil
}
func toJSONB(v interface{}) datatypes.JSON {
data, _ := json.Marshal(v)
return datatypes.JSON(data)
}

35
database/setup.go Normal file
View File

@@ -0,0 +1,35 @@
package database
import (
"fmt"
"github.com/evilsocket/islazy/log"
"gorm.io/driver/postgres"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
var (
db = (*gorm.DB)(nil)
)
func Setup(config Config) (err error) {
connString := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable",
config.Hostname,
config.Port,
config.User,
config.Password,
config.Name)
db, err = gorm.Open(postgres.Open(connString), &gorm.Config{
Logger: logger.Default.LogMode(logger.Silent),
})
if err == nil {
log.Info("connected to database")
if err = db.Debug().AutoMigrate(&Agent{}); err != nil {
return
} else if err = db.Debug().AutoMigrate(&User{}); err != nil {
return
}
}
return
}

110
database/user.go Normal file
View File

@@ -0,0 +1,110 @@
package database
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"github.com/badoux/checkmail"
"github.com/evilsocket/islazy/log"
"github.com/evilsocket/islazy/str"
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
"math/rand"
"strconv"
"time"
)
const MinPasswordLength = 8
// TODO: add 2FA
type User struct {
ID uint `gorm:"primarykey"`
CreatedAt time.Time `gorm:"index"`
UpdatedAt time.Time `gorm:"index"`
DeletedAt gorm.DeletedAt `gorm:"index"`
Email string `gorm:"index"`
Verification string `gorm:"index"`
Verified bool `gorm:"index"`
Hash string
Address string
Agents []Agent
}
func makeVerification() string {
randomShit := make([]byte, 128)
rand.Read(randomShit)
data := append(
[]byte(strconv.FormatInt(time.Now().UnixNano(), 10)),
randomShit...)
hash := sha256.Sum256(data)
return hex.EncodeToString(hash[:])
}
func RegisterUser(address, email, password string) (*User, error) {
if err := checkmail.ValidateFormat(email); err != nil {
return nil, err
} else if password = str.Trim(password); len(password) < MinPasswordLength {
return nil, fmt.Errorf("minimum password length is %d", MinPasswordLength)
}
var found User
if err := db.Where("email=?", email).First(&found).Error; err == nil {
return nil, fmt.Errorf("email address already used")
} else if err != gorm.ErrRecordNotFound {
log.Error("error searching email '%s': %v", email, err)
return nil, err
}
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return nil, fmt.Errorf("error generating password hash: %v", err)
}
newUser := User{
Email: email,
Verification: makeVerification(),
Hash: string(hashedPassword),
Address: address,
}
if err = db.Create(&newUser).Error; err != nil {
return nil, fmt.Errorf("error creating new user: %v", err)
}
return &newUser, nil
}
func VerifyUser(verification string) error {
var found User
if err := db.Where("verification=?", verification).First(&found).Error; err != nil {
return err
} else if found.Verified == true {
return fmt.Errorf("user already verified")
} else {
found.Verified = true
return db.Save(&found).Error
}
}
func LoginUser(address, email, password string) (*User, error) {
var found User
if err := db.Where("email=?", email).First(&found).Error; err == gorm.ErrRecordNotFound {
return nil, nil
} else if err != nil {
return nil, err
} else if found.Verified == false {
return nil, fmt.Errorf("account not verified")
} else if err = bcrypt.CompareHashAndPassword([]byte(found.Hash), []byte(password)); err != nil {
return nil, nil
}
found.Address = address
found.UpdatedAt = time.Now()
if err := db.Save(&found).Error; err != nil {
log.Error("error updating logged in user: %v", err)
}
return &found, nil
}

38
docker-compose.yml Normal file
View File

@@ -0,0 +1,38 @@
version: '3'
services:
database:
image: "postgres"
env_file:
- database.env
ports:
- 5432:5432
volumes:
# persist data even if container shuts down
- db:/var/lib/postgresql/data/
networks:
- shieldwall
shieldwall_api:
container_name: shieldwall_api
image: shieldwall_api:latest
build:
context: ./
dockerfile: Dockerfile
depends_on:
- database
ports:
- 8666:8666
volumes:
- ./api.yaml:/etc/shieldwall/config.yaml
restart: unless-stopped
networks:
- shieldwall
volumes:
db:
data:
networks:
shieldwall:
driver: bridge

103
firewall/logic.go Normal file
View File

@@ -0,0 +1,103 @@
package firewall
import (
"fmt"
"github.com/evilsocket/islazy/log"
"github.com/evilsocket/islazy/str"
"os/exec"
"sync"
)
// TODO: implement factory pattern with generic interface in order to support more firewalls
var lock = sync.Mutex{}
func cmd(bin string, args ...string) (string, error) {
raw, err := exec.Command(bin, args...).CombinedOutput()
if err != nil {
return "", err
} else {
return str.Trim(string(raw)), nil
}
}
func reset() error {
out, err := cmd(binary, "-F", "INPUT")
if err != nil {
return err
} else {
log.Debug("reset: %s", out)
}
return nil
}
func Reset() error {
lock.Lock()
defer lock.Unlock()
return reset()
}
func Apply(rules []Rule) (err error) {
lock.Lock()
defer lock.Unlock()
if err = reset(); err != nil {
return fmt.Errorf("error while resetting firewall: %v", err)
}
out, err := cmd(binary, "-A", "INPUT", "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j ACCEPT")
if err != nil {
return fmt.Errorf("error running conntrack step: %v", err)
} else {
log.Debug("conntrack: %s", out)
}
// for each rule
for _, rule := range rules {
protos := []string{"tcp"}
if rule.Protocol == ProtoUDP {
protos[0] = "udp"
} else {
protos = []string{"tcp", "udp"}
}
// TODO: support blocking specific addresses and maybe tracking / logging
// for each protocol
for _, proto := range protos {
for _, port := range rule.Ports {
// for each port
out, err = cmd(binary,
"-A", "INPUT",
"-s", rule.Address,
"-p", proto,
"--dport", port,
"-j", "ACCEPT")
if err != nil {
return fmt.Errorf("error applying rule %s.%s.%s.%s: %v",
rule.Type,
rule.Address,
port,
proto,
err)
} else {
log.Debug(" %s.%s.%s.%s: %s",
rule.Type,
rule.Address,
port,
proto,
out)
}
}
}
}
// drop the rest
if out, err = cmd(binary, "-A", "INPUT", "-j", "DROP"); err != nil {
return fmt.Errorf("error running drop rule: %v", err)
} else {
log.Debug("drop: %s", out)
}
return
}

20
firewall/path.go Normal file
View File

@@ -0,0 +1,20 @@
package firewall
import (
"os"
"path/filepath"
"github.com/evilsocket/islazy/log"
)
var binary = "/sbin/iptables"
func SetPath(bin string) (err error) {
if bin, err = filepath.Abs(bin); err != nil {
return
} else if _, err = os.Stat(bin); err != nil {
return
}
binary = bin
log.Info("using firewall %s", binary)
return
}

27
firewall/rule.go Normal file
View File

@@ -0,0 +1,27 @@
package firewall
const (
AllPorts = "1-65535"
)
type Protocol string
const (
ProtoTCP = Protocol("tcp")
ProtoUDP = Protocol("udp")
ProtoAll = Protocol("all")
)
type RuleType string
const (
RuleBlock = RuleType("block") // not used for now
RuleAllow = RuleType("allow")
)
type Rule struct {
Type RuleType `json:"type"` // always RuleBlock for now
Address string `json:"address"`
Protocol Protocol `json:"protocol"`
Ports []string `json:"ports"` // strings to also allow ranges
}

18
go.mod Normal file
View File

@@ -0,0 +1,18 @@
module github.com/evilsocket/shieldwall
go 1.15
require (
github.com/badoux/checkmail v1.2.1 // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
github.com/evilsocket/islazy v1.10.6
github.com/go-chi/chi v1.5.1
github.com/go-chi/cors v1.1.1
github.com/ilyakaznacheev/cleanenv v1.2.5 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect
gopkg.in/yaml.v2 v2.4.0
gorm.io/datatypes v1.0.0
gorm.io/driver/postgres v1.0.5
gorm.io/gorm v1.20.5
)

176
go.sum Normal file
View File

@@ -0,0 +1,176 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/badoux/checkmail v1.2.1 h1:TzwYx5pnsV6anJweMx2auXdekBwGr/yt1GgalIx9nBQ=
github.com/badoux/checkmail v1.2.1/go.mod h1:XroCOBU5zzZJcLvgwU15I+2xXyCdTWXyR9MGfRhBYy0=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/dgrijalva/jwt-go v1.0.2 h1:KPldsxuKGsS2FPWsNeg9ZO18aCrGKujPoWXn2yo+KQM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/evilsocket/islazy v1.10.6 h1:MFq000a1ByoumoJWlytqg0qon0KlBeUfPsDjY0hK0bo=
github.com/evilsocket/islazy v1.10.6/go.mod h1:OrwQGYg3DuZvXUfmH+KIZDjwTCbrjy48T24TUpGqVVw=
github.com/go-chi/chi v1.5.1 h1:kfTK3Cxd/dkMu/rKs5ZceWYp+t5CtiE7vmaTv3LjC6w=
github.com/go-chi/chi v1.5.1/go.mod h1:REp24E+25iKvxgeTfHmdUoL5x15kBiDBlnIl5bCwe2k=
github.com/go-chi/cors v1.1.1/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/ilyakaznacheev/cleanenv v1.2.5/go.mod h1:/i3yhzwZ3s7hacNERGFwvlhwXMDcaqwIzmayEhbRplk=
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
github.com/jackc/pgconn v1.4.0/go.mod h1:Y2O3ZDF0q4mMacyWV3AstPJpeHXWGEetiFttmq5lahk=
github.com/jackc/pgconn v1.5.0/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI=
github.com/jackc/pgconn v1.5.1-0.20200601181101-fa742c524853/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI=
github.com/jackc/pgconn v1.7.0/go.mod h1:sF/lPpNEMEOp+IYhyQGdAvrG20gWf6A1tKlr0v7JMeA=
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.0.5/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
github.com/jackc/pgtype v1.2.0/go.mod h1:5m2OfMh1wTK7x+Fk952IDmI4nw3nPrvtQdM0ZT4WpC0=
github.com/jackc/pgtype v1.3.1-0.20200510190516-8cd94a14c75a/go.mod h1:vaogEUkALtxZMCH411K+tKzNpwzCKU+AnPzBKZ+I+Po=
github.com/jackc/pgtype v1.3.1-0.20200606141011-f6355165a91c/go.mod h1:cvk9Bgu/VzJ9/lxTO5R5sf80p0DiucVtN7ZxvaC4GmQ=
github.com/jackc/pgtype v1.5.0/go.mod h1:JCULISAZBFGrHaOXIIFiyfzW5VY0GRitRr8NeJsrdig=
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
github.com/jackc/pgx/v4 v4.5.0/go.mod h1:EpAKPLdnTorwmPUUsqrPxy5fphV18j9q3wrfRXgo+kA=
github.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6fOLDxqtlyhe9UWgfIi9R8+8v8GKV5TRA/o=
github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg=
github.com/jackc/pgx/v4 v4.9.0/go.mod h1:MNGWmViCgqbZck9ujOOBN63gK9XVGILXWCvKLGKmnms=
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.2/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-sqlite3 v1.14.3/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gorm.io/datatypes v1.0.0 h1:5rDW3AnqXaacuQn6nB/ZNAIfTCIvmL5oKGa/TtCoBFA=
gorm.io/datatypes v1.0.0/go.mod h1:aKpJ+RNhLXWeF5OAdxfzBwT1UPw1wseSchF0AY3/lSw=
gorm.io/driver/mysql v1.0.3/go.mod h1:twGxftLBlFgNVNakL7F+P/x9oYqoymG3YYT8cAfI9oI=
gorm.io/driver/postgres v1.0.5/go.mod h1:qrD92UurYzNctBMVCJ8C3VQEjffEuphycXtxOudXNCA=
gorm.io/driver/sqlite v1.1.3/go.mod h1:AKDgRWk8lcSQSw+9kxCJnX/yySj8G3rdwYlU57cB45c=
gorm.io/driver/sqlserver v1.0.5/go.mod h1:WI/bfZ+s9TigYXe3hb3XjNaUP0TqmTdXl11pECyLATs=
gorm.io/gorm v1.20.1/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
gorm.io/gorm v1.20.2/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
gorm.io/gorm v1.20.4/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
gorm.io/gorm v1.20.5 h1:g3tpSF9kggASzReK+Z3dYei1IJODLqNUbOjSuCczY8g=
gorm.io/gorm v1.20.5/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
olympos.io/encoding/edn v0.0.0-20200308123125-93e3b8dd0e24/go.mod h1:oVgVk4OWVDi43qWBEyGhXgYxt7+ED4iYNpTngSLX2Iw=

9
mailer/config.go Normal file
View File

@@ -0,0 +1,9 @@
package mailer
type Config struct {
Address string `yaml:"address"`
Port int `yaml:"port"`
Username string `yaml:"username"`
Password string `yaml:"password"`
}

37
mailer/mailer.go Normal file
View File

@@ -0,0 +1,37 @@
package mailer
import (
"github.com/evilsocket/islazy/log"
"gopkg.in/gomail.v2"
"sync"
)
type Mailer struct {
sync.Mutex
conf Config
dialer *gomail.Dialer
}
func New(conf Config) (*Mailer, error) {
return &Mailer{
conf: conf,
dialer: gomail.NewDialer(conf.Address, conf.Port, conf.Username, conf.Password),
}, nil
}
func (m *Mailer) Send(from, to, subject, body string) error {
m.Lock()
defer m.Unlock()
log.Debug("sending email to %s via %s:%d ...", to, m.conf.Address, m.conf.Port)
msg := gomail.NewMessage()
msg.SetHeader("From", from)
msg.SetHeader("To", to)
msg.SetHeader("Subject", subject)
msg.SetBody("text/html", body)
return m.dialer.DialAndSend(msg)
}

3
mock-firewall.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/bin/bash
echo "iptables $@"

3
version/version.go Normal file
View File

@@ -0,0 +1,3 @@
package version
const Version = "1.0.0b"