This commit is contained in:
Bel LaPointe
2020-05-18 15:02:50 -06:00
commit fe24c6e237
16 changed files with 864 additions and 0 deletions

139
server/api.go Executable file
View File

@@ -0,0 +1,139 @@
package main
import (
"encoding/json"
"local/encryptor"
"local/system/sysconf"
"local/watchman/client/watchman"
"log"
"net/http"
)
type API struct {
conf sysconf.Conf
enc encryptor.Encryptor
storage *Storage
junk map[string]string
nextJunk string
}
func newAPI(conf sysconf.Conf) (*API, error) {
store, err := newStorage()
if err != nil {
return nil, err
}
enc := encryptor.NewEncryptor("", "")
if err := store.set(keyBucket, serviceName, enc.GetPublic()); err != nil {
return nil, err
}
return &API{
storage: store,
conf: conf,
enc: enc,
junk: make(map[string]string),
nextJunk: encryptor.NewEncryptor("", "").GetPublic(),
}, nil
}
func (api *API) ServeHTTP(w http.ResponseWriter, r *http.Request) {
httpr := &HTTPRequest{w: w, r: r}
action := httpr.PathAdvance()
if action == "getpub" {
api.pubKeyGet(httpr)
return
}
if err := httpr.readBody(api.enc); err != nil {
return
}
switch action {
case "setpub":
api.pubKeySet(httpr)
case "push":
api.push(httpr)
case "pull":
api.pull(httpr)
default:
w.WriteHeader(http.StatusNotFound)
}
}
func (api *API) pubKeyGet(httpr *HTTPRequest) {
body, err := httpr.limitReader()
if err != nil {
httpr.result(http.StatusRequestEntityTooLarge, err, nil)
return
}
if len(body) == 0 {
httpr.result(http.StatusBadRequest, nil, nil)
return
}
// if asks for server pub, then return server pub
if string(body) == serviceName {
httpr.result(http.StatusOK, nil, api.enc.GetPublic())
return
}
// else if encrypted with server's pub, then return any
if decrypted := api.enc.Decrypt(string(body)); decrypted != "" {
if pub, err := api.storage.get(keyBucket, decrypted); err == nil {
httpr.result(http.StatusOK, nil, pub)
}
httpr.result(http.StatusNotFound, nil, nil)
return
}
// else return garbage
if v, ok := api.junk[string(body)]; ok {
httpr.result(secretError, nil, v)
} else {
api.junk[string(body)] = api.nextJunk
httpr.result(secretError, nil, api.junk[string(body)])
go func() {
api.nextJunk = encryptor.NewEncryptor("", "").GetPublic()
}()
}
}
func (api *API) pubKeySet(httpr *HTTPRequest) {
var setRequest struct {
Key string `json:"key"`
ID string `json:"id"`
}
if err := json.Unmarshal([]byte(httpr.body), &setRequest); err != nil {
httpr.result(http.StatusBadRequest, nil, nil)
return
}
api.storage.set(keyBucket, setRequest.ID, setRequest.Key)
}
func (api *API) push(httpr *HTTPRequest) {
var pack watchman.Package
if err := json.Unmarshal([]byte(httpr.body), &pack); err != nil {
httpr.result(http.StatusBadRequest, err, nil)
return
}
if err := api.storage.set("objects", storePath(pack), httpr.body); err != nil {
httpr.result(http.StatusInternalServerError, err, nil)
return
}
httpr.result(http.StatusOK, nil, nil)
}
func (api *API) pull(httpr *HTTPRequest) {
var pack watchman.Package
if err := json.Unmarshal([]byte(httpr.body), &pack); err != nil {
httpr.result(http.StatusBadRequest, err, nil)
return
}
b, err := api.storage.get("objects", storePath(pack))
if err != nil {
httpr.result(http.StatusNotFound, err, nil)
return
}
enc := encryptor.NewEncryptor("", "")
enc.SetSymmetric(string(pack.Packs[0]))
encrypted := enc.Encrypt(string(b))
log.Print("returning content", string(b))
httpr.result(http.StatusOK, nil, encrypted)
}
func storePath(pack watchman.Package) string {
return pack.ID + "_" + pack.Path
}

18
server/cert.pem Executable file
View File

@@ -0,0 +1,18 @@
-----BEGIN CERTIFICATE-----
MIIC9zCCAd+gAwIBAgIQcgF95XkV06ypg+WfUhEegTANBgkqhkiG9w0BAQsFADAS
MRAwDgYDVQQKEwdBY21lIENvMB4XDTE4MDgxMDEzNTQyNVoXDTE5MDgxMDEzNTQy
NVowEjEQMA4GA1UEChMHQWNtZSBDbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
AQoCggEBANQTRy/WN7m5H/E+T5eBo8UfFrTgMq1DK0dE9vBSs5yNgxb8qlXUJXsl
gTMOdBeijP4g47EMUMy2lOgAqeY8aWYvkq9hahnew7tpdKNd94+PKhPAN8NMbMH2
MHSCUN9MZq+cj33ieGstf51z5ANhb9aP0HLbU9o549od/5QLOdklfopmwz9DGY3b
K2kzmBrH7n26CPmdxjKaemW6IiZU4SJBhCf3A/371IfZMk6EHZhZ+5TTl2mXyV/v
QBqZHGh5+nTccCVnHIRUrRgA0fqrxVuzgU/3EoHDihC60I0EynT+bFWp7TSsrNWX
K4ibFQg8YbTcyqszNWm3BlnM6ThF5G8CAwEAAaNJMEcwDgYDVR0PAQH/BAQDAgWg
MBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwEgYDVR0RBAswCYIH
Y2hpcmFtZTANBgkqhkiG9w0BAQsFAAOCAQEAlhOgt0biHgqiBg4V7cEcwfGrSwd4
l4rT1HkmrTG4AKfPYKDMd7KcBOhkNMq89nQkQ3cZBM8s8JIzkpK4nqYCi0wIAAEf
QUJyCH9BwCTBZO7BIl3NTwQnx8yqmLesRnpCJo/hrbO8gZANO+1BCCov7Vl0jblE
4jbyP33xPUpTi7LPqoNSQVIaEoRj1B7BxViyYw2x5cpL6J52IKpSAnOPFqgeQxzz
CwFGuhPERZ8H0Fr9EfcxJzY3PFxyOHnsPizrQVFfrGkSZftIjGuwELhs/qJ4FAJ3
oqLEXfOcjoJw/ebd5v+c0eQoNlmLeewWlE0p/zVzui2YVNOR0C4uqnM+Fg==
-----END CERTIFICATE-----

24
server/entrypoint.sh Executable file
View File

@@ -0,0 +1,24 @@
#! /bin/bash
set -e
function cleanDocker() {
echo "not killing"
#echo Killing $CONTAINER_NAME
#docker kill $CONTAINER_NAME
}
trap cleanDocker EXIT
cd "$(dirname ${BASH_SOURCE[0]})"
if [ ! -f ./key.pem ] || [ ! -f ./cert.pem ]; then
rm -f ./key.pem ./cert.pem
go run ./vendor/crypto/tls/generate_cert.go --host chirame
fi
go build
source ./start.minio
./$(basename $(pwd))

60
server/httpr.go Executable file
View File

@@ -0,0 +1,60 @@
package main
import (
"errors"
"fmt"
"io/ioutil"
"local/encryptor"
"log"
"net/http"
"path"
"strings"
)
type HTTPRequest struct {
w http.ResponseWriter
r *http.Request
body string
}
func (httpr *HTTPRequest) PathAdvance() string {
p := path.Clean("/" + httpr.r.URL.Path)
i := strings.Index(p[1:], "/") + 1
if i <= 0 {
httpr.r.URL.Path = "/"
return p[1:]
}
httpr.r.URL.Path = p[i:]
return p[1:i]
}
func (httpr *HTTPRequest) result(status int, err error, body interface{}) {
if err != nil {
log.Print(err)
}
httpr.w.WriteHeader(status)
if body != nil {
fmt.Fprintln(httpr.w, body)
}
}
func (httpr *HTTPRequest) limitReader(n ...int) ([]byte, error) {
if len(n) < 1 {
return ioutil.ReadAll(http.MaxBytesReader(nil, httpr.r.Body, int64(2048)))
}
return ioutil.ReadAll(http.MaxBytesReader(nil, httpr.r.Body, int64(n[0])))
}
func (httpr *HTTPRequest) readBody(enc encryptor.Encryptor) error {
body, err := httpr.limitReader()
if err != nil {
httpr.result(http.StatusRequestEntityTooLarge, err, nil)
return err
}
httpr.body = enc.Decrypt(string(body))
if httpr.body == "" {
httpr.result(secretError, nil, nil)
return errors.New("wrong pub key")
}
return nil
}

27
server/key.pem Executable file
View File

@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA1BNHL9Y3ubkf8T5Pl4GjxR8WtOAyrUMrR0T28FKznI2DFvyq
VdQleyWBMw50F6KM/iDjsQxQzLaU6ACp5jxpZi+Sr2FqGd7Du2l0o133j48qE8A3
w0xswfYwdIJQ30xmr5yPfeJ4ay1/nXPkA2Fv1o/QcttT2jnj2h3/lAs52SV+imbD
P0MZjdsraTOYGsfufboI+Z3GMpp6ZboiJlThIkGEJ/cD/fvUh9kyToQdmFn7lNOX
aZfJX+9AGpkcaHn6dNxwJWcchFStGADR+qvFW7OBT/cSgcOKELrQjQTKdP5sVant
NKys1ZcriJsVCDxhtNzKqzM1abcGWczpOEXkbwIDAQABAoIBAF8EgCmTrgX9Rq18
wIZeJDUmd7L0nF/6r0JQSN9l/mlPEgPTkrG/ykdBh4CLokIQp2EY9UsW/ICr8U19
NqIcQRDykaMYX0RWBZZLamHjsQ5WE4Ej5xgOfs/scMtSs89IWN2npLa/KDrE+N9f
1DIbjtMwPjGnyQsGUusf86mt2e5KyG6wljBC4QfxyDpfkj4VD4Gx45poeeWaQEhp
vHWSWwbbTAGwkgriLh68Y/fxg/NNXTgx0O03oukZR7JM2zAKlNFegcsiPsVa4KbG
tJeszezIyLIHW+CH42jxHPqwgITdEWmIMvvwi1faoE27Td8NwU1IjeWIHRvW66o7
w6ZwQyECgYEA4/v6fS6MIMttRhrodGIsCHewN+iesVy/uzuVV+Im1w223ziMLanR
jBu458HEh3QC84T/ZziLtq1te9B+QWT8lCZ75Hje5QpPBgj4w2+AqueHeMPCOGZ2
5hnZzWBhTAaRmY7yGDcXQ1V01dAbxWz6D6HYHktg0f9VhbmNfWuladECgYEA7iLU
DO9AsdGnQMCaWZzoZdjUZ6GrdcQ3O60B71EsBSt+lEHGik8AfJF0QKSJbanr4It/
9UxSgiFTVYQ1ZiKJzGrJqZ/+n3TYrIuvUTFh29UtE07RwAfb+O+YMBW47AldRqOn
5hzGDwIZs8N38oQzts6WnmOffAK+jNSZK31/uj8CgYEAzrHP6xh9cOod+xZAM+wh
kde5iya6YYD+T2j4wEHIBudnKb/hzzCMS+OCY5PYcxnp5xBoYhPxD0Dy5vMi1HUT
TdoKLxyqsKsE9CrEJqP72nao6wNIHcw/9ePwBHRiIgQ+kyL3OJ8R1zkuAP95fieM
GwoXn3elox5EUkXlEpW61VECgYBOqCRgjVpSIczb34JcHS7KDT/DZywqPwB7bp7X
/HjM0FwD/mHk50li2+yJOY/HMDwgNBO041vRbc6HzZ6RuNDJO3CW3akN5Ft5Sr5C
1Evdf+Feokc35aCr7f/XyiUFmeY8YewgXtqwtGHm9aaV7ULjnAM2F/Pi00k7XTGm
otJgowKBgQDCxx0oqEvq9rJvRzGSlQyX2y0Zdw8clZKRVBeHGirCg/0iRzn9cQ2x
Dn5r2XSfPUpMw/MEO2175PwTqmNtzR7WfPye2FUdbn4zaS9mHqcFz2774Me/NTNf
V404gY4ZhI3k2Jie0cavFBI1FDI7pQVf2roYnKgPhF4xUHvLcj87FA==
-----END RSA PRIVATE KEY-----

41
server/main.go Executable file
View File

@@ -0,0 +1,41 @@
package main
import (
"local/system/sysconf"
"log"
"net/http"
"strings"
)
var serviceName = sysconf.Get("watchmans").Name
const secretError = http.StatusOK
func redirHandle(w http.ResponseWriter, r *http.Request) {
host := strings.Split(r.Host, ":")[0] + ":" + strings.Split(sysconf.Get(serviceName).Port, ",")[0]
target := "https://" + host + r.URL.Path
if len(r.URL.RawQuery) > 0 {
target += "?" + r.URL.RawQuery
}
log.Print(target)
http.Redirect(w, r, target, http.StatusTemporaryRedirect)
}
func main() {
conf := sysconf.Get(serviceName)
ports := strings.Split(conf.Port, ",")
if len(ports) < 2 {
panic("not enough ports in sysconf")
}
log.Print("making new API")
api, err := newAPI(conf)
if err != nil {
log.Fatal(err)
}
log.Printf("Listening for http on %v", ports[0])
go func() {
log.Fatal(http.ListenAndServe(":"+ports[0], http.HandlerFunc(redirHandle)))
}()
log.Printf("Listening for https on %v", ports[1])
log.Fatal(http.ListenAndServeTLS(":"+ports[1], "./cert.pem", "./key.pem", api))
}

54
server/start.minio Executable file
View File

@@ -0,0 +1,54 @@
#! /bin/bash
set -u
set -e
function start_minio() {
cd "$(dirname ${BASH_SOURCE[0]})"
AWS_ACCESS="${user:-}"
AWS_SECRET="${pass:-}"
AWS_PORT="${AWS_PORT:-11000}"
AWS_REGION="local"
CONTAINER_NAME="watchman-minio"
if [ "$(docker ps | grep $CONTAINER_NAME)" == "" ]; then
echo "Starting minio..."
docker run -d --rm \
-p $AWS_PORT:$AWS_PORT \
-e MINIO_ACCESS_KEY="$AWS_ACCESS" \
-e MINIO_SECRET_KEY="$AWS_SECRET" \
-e MINIO_REGION="$AWS_REGION" \
--name $CONTAINER_NAME \
minio/minio \
server --address ":$AWS_PORT" /data
sleep 10
else
echo "minio already running"
fi
keys="$(docker exec $CONTAINER_NAME cat /root/.minio/config.json | grep Key)"
STORE_ID="${keys#*: \"}"
STORE_ID="${STORE_ID%%\"*}"
STORE_LOC="localhost:$AWS_PORT"
STORE_SECRET="${keys##*secretKey\": \"}"
STORE_SECRET="${STORE_SECRET%%\"*}"
export STORE_ID="$STORE_ID"
export STORE_SECRET="$STORE_SECRET"
export STORE_LOC="$STORE_LOC"
export STORE_REGION="$AWS_REGION"
echo "Waiting for minio..."
skip=5
for((i=0; i<60; i+=skip)); do
printf "\tcheck %d..." $((i/skip))
if [ "$(docker ps | grep $CONTAINER_NAME | grep "(healthy)")" != "" ]; then
echo ""
return
fi
sleep $skip
done
echo "\nMinio never healthy"
docker logs $CONTAINER_NAME
exit 1
}
start_minio

185
server/storage.go Executable file
View File

@@ -0,0 +1,185 @@
package main
import (
"bytes"
"errors"
"fmt"
"io/ioutil"
"log"
"os"
"strings"
"time"
minio "github.com/minio/minio-go"
)
var nilLocation = os.Getenv("STORE_REGION")
const keyBucket = "key"
var bucketNames = []string{}
type Storage struct {
client *minio.Client
}
func assertEnv(key string) (string, error) {
value := os.Getenv(key)
if value == "" {
return "", errors.New(key + " not set")
}
return value, nil
}
func newStorage() (*Storage, error) {
var id, loc, secret string
var err error
if id, err = assertEnv("STORE_ID"); err != nil {
return nil, err
}
if secret, err = assertEnv("STORE_SECRET"); err != nil {
return nil, err
}
if loc, err = assertEnv("STORE_LOC"); err != nil {
return nil, err
}
if _, err = assertEnv("STORE_REGION"); err != nil {
return nil, err
}
client, err := minio.New(loc, id, secret, false)
if err != nil {
return nil, err
}
if err := makeInitialBuckets(client); err != nil {
return nil, err
}
return &Storage{
client: client,
}, err
}
func makeInitialBuckets(client *minio.Client) error {
buckets, err := client.ListBuckets()
if err != nil {
return err
}
keysFound := false
for _, bucketInfo := range buckets {
if bucketInfo.Name == keyBucket {
keysFound = true
}
bucketNames = append(bucketNames, bucketInfo.Name)
}
if !keysFound {
bucketNames = append(bucketNames, keyBucket)
}
for _, bucketName := range bucketNames {
if err := makeMinioBucket(client, bucketName); err != nil {
return err
}
}
return nil
}
func makeMinioBucket(client *minio.Client, bucketName string) error {
log.Print("Making bucket", bucketName, "...")
if err := client.MakeBucket(bucketName, nilLocation); err == nil {
log.Print("...Made", bucketName)
return nil
}
exists, err := client.BucketExists(bucketName)
if err == nil && exists {
log.Print("...Exists", bucketName)
return nil
}
log.Print("...Can't make bucket", bucketName, err)
return err
}
func (s *Storage) makeBucket(bucket string) error {
if bucket == keyBucket {
return errors.New("reserved bucket name")
}
err := makeMinioBucket(s.client, bucket)
if err == nil {
bucketNames = append(bucketNames, bucket)
}
return err
}
func (s *Storage) toGetKey(fname, id string) string {
return fmt.Sprintf(
"%v_%v",
id,
strings.Split(fname, ".")[0],
)
}
func (s *Storage) toSetKey(fname, id string) string {
return fmt.Sprintf(
"%v_%v.%v",
s.toGetKey(fname, id),
time.Now().UnixNano(),
strings.Split(fname, ".")[1],
)
}
func (s *Storage) set(bucket, key, value string) error {
key = encodeKey(key)
log.Print("server setting in minio")
if !validBucket(bucket) {
log.Print("making bucket", bucket)
if err := s.makeBucket(bucket); err != nil {
log.Print("cantmake bucket", bucket)
return err
}
}
log.Print("putting", key)
_, err := s.client.PutObject(bucket, key, bytes.NewBuffer([]byte(value)), int64(len(value)), minio.PutObjectOptions{})
log.Print("put err:", err)
return err
}
func (s *Storage) get(bucket, key string) (string, error) {
key = encodeKey(key)
if !validBucket(bucket) {
return "", errors.New("unknown bucket")
}
done := make(chan struct{})
defer close(done)
lastKey := ""
for obj := range s.client.ListObjectsV2(bucket, key, false, done) {
if lastKey == "" {
lastKey = obj.Key
}
if obj.Key > lastKey {
lastKey = obj.Key
}
}
if lastKey == "" {
return "", errors.New("object not found")
}
obj, err := s.client.GetObject(bucket, lastKey, minio.GetObjectOptions{})
if err != nil {
return "", err
}
body, err := ioutil.ReadAll(obj)
return string(body), err
}
func validBucket(bucket string) bool {
for i := range bucketNames {
if bucketNames[i] == bucket {
return true
}
}
return false
}
func encodeKey(key string) string {
return strings.Replace(key, "/", "`", -1)
}
func decodeKey(key string) string {
return strings.Replace(key, "`", "/", -1)
}