diff --git a/README.md b/README.md index de0b3e9..3754a7c 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,8 @@ Usage of shhttp: If non-empty, write log files in this directory -logtostderr log to standard error instead of files + -password string + HTTP API password -port int port to listen on (default 2112) -stderrthreshold value diff --git a/cmd/shhttp/main.go b/cmd/shhttp/main.go index e37e426..3f9d3e0 100644 --- a/cmd/shhttp/main.go +++ b/cmd/shhttp/main.go @@ -17,6 +17,7 @@ func main() { cleanup := flag.Int("clean-interval", -1, "interval (hours) after which finished jobs are cleaned") location := flag.String("dir", "shhttp", "location to store the job data") revive := flag.Bool("revive", false, "Whether to revive previous running jobs if there are any") + password := flag.String("password", "", "HTTP API password") flag.Parse() @@ -40,7 +41,7 @@ func main() { }() } - router := pkg.GetRouter(jobStore, savedJobStore, *revive) + router := pkg.GetRouter(jobStore, savedJobStore, *revive, *password) address := strings.Join([]string{*hostname, strconv.Itoa(*port)}, ":") glog.Infof("starting HTTP listener at %s", address) glog.Fatal(http.ListenAndServe(address, router)) diff --git a/pkg/functions.go b/pkg/functions.go index 0d6b6bb..0875fca 100644 --- a/pkg/functions.go +++ b/pkg/functions.go @@ -200,7 +200,7 @@ func (j FileBasedJobStore) DeleteJob(id string) error { } } -func GetRouter(jobStore JobStore, savedJobStore JobStore, revive bool) *mux.Router { +func GetRouter(jobStore JobStore, savedJobStore JobStore, revive bool, password string) *mux.Router { router := mux.NewRouter() jobQueue := make(chan *Job) @@ -312,6 +312,14 @@ func GetRouter(jobStore JobStore, savedJobStore JobStore, revive bool) *mux.Rout writeErrorResponse(err, http.StatusBadRequest, writer) return } + if executable.Password != password { + err := errors.New("password invalid"); + glog.Error(err) + writeErrorResponse(err, http.StatusUnauthorized, writer) + return + } + // Omit the "Password" field when marshaling if any. + executable.Password = "" result := ExecResult{Executable: &executable} Execute(&result) respData, err := json.Marshal(result) @@ -333,18 +341,47 @@ func GetRouter(jobStore JobStore, savedJobStore JobStore, revive bool) *mux.Rout writeErrorResponse(err, http.StatusBadRequest, writer) return } + if job.Password != password { + err := errors.New("password invalid"); + glog.Error(err) + writeErrorResponse(err, http.StatusUnauthorized, writer) + return + } + // Omit the "Password" field when marshaling if any. + job.Password = "" runJob(writer, request, &job, queue) }) router.Path("/v1/jobs").Methods(http.MethodGet).HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + q := request.URL.Query() + if q.Get("password") != password { + err := errors.New("password invalid"); + glog.Error(err) + writeErrorResponse(err, http.StatusUnauthorized, writer) + return + } getIds(writer, request, jobStore) }) router.Path("/v1/jobs/{id}").Methods(http.MethodGet).HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + q := request.URL.Query() + if q.Get("password") != password { + err := errors.New("password invalid"); + glog.Error(err) + writeErrorResponse(err, http.StatusUnauthorized, writer) + return + } getJob(writer, request, jobStore) }) router.Path("/v1/saved").Methods(http.MethodGet).HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + q := request.URL.Query() + if q.Get("password") != password { + err := errors.New("password invalid"); + glog.Error(err) + writeErrorResponse(err, http.StatusUnauthorized, writer) + return + } getIds(writer, request, savedJobStore) }) @@ -357,6 +394,14 @@ func GetRouter(jobStore JobStore, savedJobStore JobStore, revive bool) *mux.Rout writeErrorResponse(err, http.StatusBadRequest, writer) return } + if job.Password != password { + err := errors.New("password invalid"); + glog.Error(err) + writeErrorResponse(err, http.StatusUnauthorized, writer) + return + } + // Omit the "Password" field when marshaling if any. + job.Password = "" if job.Id == "" { job.Id = uuid.New().String() } @@ -367,6 +412,13 @@ func GetRouter(jobStore JobStore, savedJobStore JobStore, revive bool) *mux.Rout router.Path("/v1/saved/{id}").Methods(http.MethodDelete).HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { writeContentType(writer) + q := request.URL.Query() + if q.Get("password") != password { + err := errors.New("password invalid"); + glog.Error(err) + writeErrorResponse(err, http.StatusUnauthorized, writer) + return + } vars := mux.Vars(request) id := vars["id"] err := savedJobStore.DeleteJob(id) @@ -378,6 +430,13 @@ func GetRouter(jobStore JobStore, savedJobStore JobStore, revive bool) *mux.Rout }) router.Path("/v1/saved/{id}").Methods(http.MethodGet).HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + q := request.URL.Query() + if q.Get("password") != password { + err := errors.New("password invalid"); + glog.Error(err) + writeErrorResponse(err, http.StatusUnauthorized, writer) + return + } getJob(writer, request, savedJobStore) }) @@ -399,6 +458,14 @@ func GetRouter(jobStore JobStore, savedJobStore JobStore, revive bool) *mux.Rout if err != nil { glog.Error(err) } else { + if runBody.Password != password { + err := errors.New("password invalid"); + glog.Error(err) + writeErrorResponse(err, http.StatusUnauthorized, writer) + return + } + // Omit the "Password" field when marshaling if any. + runBody.Password = "" updateJobEnv(job, runBody.Env) } runJob(writer, request, job, queue) diff --git a/pkg/structs.go b/pkg/structs.go index 557a74b..c3c6010 100644 --- a/pkg/structs.go +++ b/pkg/structs.go @@ -1,12 +1,13 @@ package pkg type Executable struct { - Command string - Args []string - BaseDir string - Stdin string - Shell bool - Env map[string]string + Command string + Args []string + BaseDir string + Stdin string + Shell bool + Env map[string]string + Password string `json:"Password,omitempty"` } type ExecResult struct { @@ -34,10 +35,12 @@ type Job struct { Created int64 LastModified int64 IgnoreErrors bool + Password string `json:"Password,omitempty"` } type RunBody struct { - Env map[string]string + Env map[string]string + Password string `json:"Password,omitempty"` } type IdsResponse struct {