2 Commits

  1. 80
      api/command.go
  2. 44
      client/client.go
  3. 29
      main.go
  4. 72
      server/apicommand.go
  5. 14
      server/http.go

80
api/command.go

@ -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
}

44
client/client.go

@ -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

@ -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])
}

72
server/apicommand.go

@ -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
}

14
server/http.go

@ -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…
Cancel
Save