mirror of
https://github.com/evilsocket/shieldwall.git
synced 2021-09-07 00:28:37 +03:00
first commit
This commit is contained in:
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
_build
|
||||
.DS_Store
|
||||
.idea
|
||||
dist
|
||||
agent.yaml
|
||||
api.yaml
|
||||
23
Dockerfile
Normal file
23
Dockerfile
Normal 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
22
Makefile
Normal 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
44
api/agent_get_rules.go
Normal 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
16
api/config.go
Normal 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
59
api/setup.go
Normal 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
67
api/user_login.go
Normal 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
56
api/user_register.go
Normal 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
22
api/user_verify.go
Normal 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
124
api/utils.go
Normal 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
64
cmd/agent/api.go
Normal 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
39
cmd/agent/config.go
Normal 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
108
cmd/agent/main.go
Normal 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
40
cmd/agent/setup.go
Normal 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
61
cmd/agent/state.go
Normal 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
23
cmd/api/config.go
Normal 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
40
cmd/api/main.go
Normal 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
40
cmd/api/setup.go
Normal 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
3
database.env
Normal file
@@ -0,0 +1,3 @@
|
||||
POSTGRES_USER=shieldwall
|
||||
POSTGRES_PASSWORD=shieldwall
|
||||
POSTGRES_DB=shieldwall
|
||||
36
database/agent.go
Normal file
36
database/agent.go
Normal 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
9
database/config.go
Normal 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
26
database/jsonb.go
Normal 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
35
database/setup.go
Normal 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
110
database/user.go
Normal 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
38
docker-compose.yml
Normal 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
103
firewall/logic.go
Normal 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
20
firewall/path.go
Normal 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
27
firewall/rule.go
Normal 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
18
go.mod
Normal 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
176
go.sum
Normal 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
9
mailer/config.go
Normal 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
37
mailer/mailer.go
Normal 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
3
mock-firewall.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "iptables $@"
|
||||
3
version/version.go
Normal file
3
version/version.go
Normal file
@@ -0,0 +1,3 @@
|
||||
package version
|
||||
|
||||
const Version = "1.0.0b"
|
||||
Reference in New Issue
Block a user