Compare commits
2 Commits
5571401103
...
aa65b64867
Author | SHA1 | Date |
---|---|---|
Christopher Ramey | aa65b64867 | |
Christopher Ramey | 86f304e1b8 |
|
@ -0,0 +1,80 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Command struct {
|
||||||
|
Expires time.Time `json:"exp"`
|
||||||
|
Command string `json:"cmd"`
|
||||||
|
Scheme string `json:"sch"`
|
||||||
|
Signature []byte `json:"sig,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCommand(cm string) *Command {
|
||||||
|
return &Command{
|
||||||
|
Expires: time.Now().Add(time.Second * 5),
|
||||||
|
Command: cm,
|
||||||
|
Scheme: "hmac-sha256",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseCommand(jsn []byte) (*Command, error) {
|
||||||
|
cmd := &Command{}
|
||||||
|
err := json.Unmarshal(jsn, cmd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cmd, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Command) JSON() ([]byte, error) {
|
||||||
|
return json.Marshal(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Command) Sign(key []byte) error {
|
||||||
|
switch c.Scheme {
|
||||||
|
case "hmac-sha256":
|
||||||
|
j, err := c.JSON()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("json encoding error")
|
||||||
|
}
|
||||||
|
|
||||||
|
mac := hmac.New(sha256.New, key)
|
||||||
|
mac.Write(j)
|
||||||
|
c.Signature = mac.Sum(nil)
|
||||||
|
|
||||||
|
case "":
|
||||||
|
return fmt.Errorf("scheme may not be empty")
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported scheme: %s", c.Scheme)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Command) Validate(key []byte) error {
|
||||||
|
cpy := &Command{
|
||||||
|
Expires: c.Expires,
|
||||||
|
Command: c.Command,
|
||||||
|
Scheme: c.Scheme,
|
||||||
|
}
|
||||||
|
err := cpy.Sign(key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hmac.Equal(cpy.Signature, c.Signature) {
|
||||||
|
return fmt.Errorf("invalid signature")
|
||||||
|
}
|
||||||
|
|
||||||
|
if time.Now().After(c.Expires) {
|
||||||
|
return fmt.Errorf("command expired")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.binarythought.com/cdramey/alrm/api"
|
||||||
|
"git.binarythought.com/cdramey/alrm/config"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Shutdown(cfg *config.Config) error {
|
||||||
|
return doCommand(cfg, "shutdown")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Restart(cfg *config.Config) error {
|
||||||
|
return doCommand(cfg, "restart")
|
||||||
|
}
|
||||||
|
|
||||||
|
func doCommand(cfg *config.Config, cm string) error {
|
||||||
|
aurl, err := url.Parse("http://" + cfg.Listen + "/api")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := api.NewCommand(cm)
|
||||||
|
err = cmd.Sign(cfg.APIKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cjson, err := cmd.JSON()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
params := url.Values{}
|
||||||
|
params.Add("cmd", string(cjson))
|
||||||
|
|
||||||
|
resp, err := http.PostForm(aurl.String(), params)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
29
main.go
29
main.go
|
@ -6,6 +6,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"git.binarythought.com/cdramey/alrm/config"
|
"git.binarythought.com/cdramey/alrm/config"
|
||||||
"git.binarythought.com/cdramey/alrm/server"
|
"git.binarythought.com/cdramey/alrm/server"
|
||||||
|
"git.binarythought.com/cdramey/alrm/client"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
@ -130,6 +131,32 @@ func main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "shutdown":
|
||||||
|
cfg, err := config.ReadConfig(*cfgpath, 0)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = client.Shutdown(cfg)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
case "restart":
|
||||||
|
cfg, err := config.ReadConfig(*cfgpath, 0)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = client.Restart(cfg)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
case "help", "":
|
case "help", "":
|
||||||
printUsage()
|
printUsage()
|
||||||
|
|
||||||
|
@ -149,4 +176,6 @@ func printUsage() {
|
||||||
fmt.Printf(" run a check manually: %s [args] check <host/group>\n", os.Args[0])
|
fmt.Printf(" run a check manually: %s [args] check <host/group>\n", os.Args[0])
|
||||||
fmt.Printf(" test an alarm: %s [args] alarm <name>\n", os.Args[0])
|
fmt.Printf(" test an alarm: %s [args] alarm <name>\n", os.Args[0])
|
||||||
fmt.Printf(" start server (forground): %s [args] server\n", os.Args[0])
|
fmt.Printf(" start server (forground): %s [args] server\n", os.Args[0])
|
||||||
|
fmt.Printf(" shutdown server: %s [args] shutdown\n", os.Args[0])
|
||||||
|
fmt.Printf(" restart server: %s [args] restart\n", os.Args[0])
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,72 +0,0 @@
|
||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/hmac"
|
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type APICommand struct {
|
|
||||||
Expires time.Time `json:"exp"`
|
|
||||||
Command string `json:"cmd"`
|
|
||||||
Scheme string `json:"sch"`
|
|
||||||
Signature []byte `json:"sig,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseAPICommand(jsn []byte) (*APICommand, error) {
|
|
||||||
api := &APICommand{}
|
|
||||||
err := json.Unmarshal(jsn, api)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return api, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ac *APICommand) JSON() ([]byte, error) {
|
|
||||||
return json.Marshal(ac)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ac *APICommand) Sign(key []byte) error {
|
|
||||||
switch ac.Scheme {
|
|
||||||
case "hmac-sha256":
|
|
||||||
j, err := ac.JSON()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("json encoding error")
|
|
||||||
}
|
|
||||||
|
|
||||||
mac := hmac.New(sha256.New, key)
|
|
||||||
mac.Write(j)
|
|
||||||
ac.Signature = mac.Sum(nil)
|
|
||||||
|
|
||||||
case "":
|
|
||||||
return fmt.Errorf("scheme may not be empty")
|
|
||||||
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unsupported scheme: %s", ac.Scheme)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ac *APICommand) Validate(key []byte) error {
|
|
||||||
cpy := &APICommand{
|
|
||||||
Expires: ac.Expires,
|
|
||||||
Command: ac.Command,
|
|
||||||
Scheme: ac.Scheme,
|
|
||||||
}
|
|
||||||
err := cpy.Sign(key)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !hmac.Equal(cpy.Signature, ac.Signature) {
|
|
||||||
return fmt.Errorf("invalid signature")
|
|
||||||
}
|
|
||||||
|
|
||||||
if time.Now().After(ac.Expires) {
|
|
||||||
return fmt.Errorf("command expired")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -2,21 +2,27 @@ package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"git.binarythought.com/cdramey/alrm/api"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
switch r.URL.Path {
|
switch r.URL.Path {
|
||||||
case "/api":
|
case "/api":
|
||||||
g := r.URL.Query()
|
if err := r.ParseForm(); err != nil {
|
||||||
c := g.Get("cmd")
|
http.Error(w, fmt.Sprintf("form parse error: %s", err.Error()),
|
||||||
|
http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c := r.FormValue("cmd")
|
||||||
if c == "" {
|
if c == "" {
|
||||||
http.Error(w, "no command given", http.StatusBadRequest)
|
http.Error(w, "no command given", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cmd, err := ParseAPICommand([]byte(c))
|
cmd, err := api.ParseCommand([]byte(c))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, fmt.Sprintf("error parsing command: %s", err.Error()),
|
http.Error(w, fmt.Sprintf("command parse error: %s", err.Error()),
|
||||||
http.StatusBadRequest)
|
http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue