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"
|
||||
"git.binarythought.com/cdramey/alrm/config"
|
||||
"git.binarythought.com/cdramey/alrm/server"
|
||||
"git.binarythought.com/cdramey/alrm/client"
|
||||
"os"
|
||||
"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", "":
|
||||
printUsage()
|
||||
|
||||
|
@ -149,4 +176,6 @@ func printUsage() {
|
|||
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(" 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 (
|
||||
"fmt"
|
||||
"git.binarythought.com/cdramey/alrm/api"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/api":
|
||||
g := r.URL.Query()
|
||||
c := g.Get("cmd")
|
||||
if err := r.ParseForm(); err != nil {
|
||||
http.Error(w, fmt.Sprintf("form parse error: %s", err.Error()),
|
||||
http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
c := r.FormValue("cmd")
|
||||
if c == "" {
|
||||
http.Error(w, "no command given", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
cmd, err := ParseAPICommand([]byte(c))
|
||||
cmd, err := api.ParseCommand([]byte(c))
|
||||
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)
|
||||
return
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue