mirror of
https://github.com/goharbor/harbor
synced 2025-04-07 21:30:15 +00:00

Add validation to job ID in the API to get job log in job service, to prevent file path traversal attack. Signed-off-by: Daniel Jiang <jiangd@vmware.com>
254 lines
6.5 KiB
Go
254 lines
6.5 KiB
Go
// Copyright 2018 The Harbor Authors. All rights reserved.
|
|
|
|
package api
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/gorilla/mux"
|
|
|
|
"github.com/vmware/harbor/src/jobservice/core"
|
|
"github.com/vmware/harbor/src/jobservice/errs"
|
|
"github.com/vmware/harbor/src/jobservice/models"
|
|
"github.com/vmware/harbor/src/jobservice/opm"
|
|
)
|
|
|
|
//Handler defines approaches to handle the http requests.
|
|
type Handler interface {
|
|
//HandleLaunchJobReq is used to handle the job submission request.
|
|
HandleLaunchJobReq(w http.ResponseWriter, req *http.Request)
|
|
|
|
//HandleGetJobReq is used to handle the job stats query request.
|
|
HandleGetJobReq(w http.ResponseWriter, req *http.Request)
|
|
|
|
//HandleJobActionReq is used to handle the job action requests (stop/retry).
|
|
HandleJobActionReq(w http.ResponseWriter, req *http.Request)
|
|
|
|
//HandleCheckStatusReq is used to handle the job service healthy status checking request.
|
|
HandleCheckStatusReq(w http.ResponseWriter, req *http.Request)
|
|
|
|
//HandleJobLogReq is used to handle the request of getting job logs
|
|
HandleJobLogReq(w http.ResponseWriter, req *http.Request)
|
|
}
|
|
|
|
//DefaultHandler is the default request handler which implements the Handler interface.
|
|
type DefaultHandler struct {
|
|
controller core.Interface
|
|
}
|
|
|
|
//NewDefaultHandler is constructor of DefaultHandler.
|
|
func NewDefaultHandler(ctl core.Interface) *DefaultHandler {
|
|
return &DefaultHandler{
|
|
controller: ctl,
|
|
}
|
|
}
|
|
|
|
//HandleLaunchJobReq is implementation of method defined in interface 'Handler'
|
|
func (dh *DefaultHandler) HandleLaunchJobReq(w http.ResponseWriter, req *http.Request) {
|
|
if !dh.preCheck(w) {
|
|
return
|
|
}
|
|
|
|
data, err := ioutil.ReadAll(req.Body)
|
|
if err != nil {
|
|
dh.handleError(w, http.StatusInternalServerError, errs.ReadRequestBodyError(err))
|
|
return
|
|
}
|
|
|
|
//unmarshal data
|
|
jobReq := models.JobRequest{}
|
|
if err = json.Unmarshal(data, &jobReq); err != nil {
|
|
dh.handleError(w, http.StatusInternalServerError, errs.HandleJSONDataError(err))
|
|
return
|
|
}
|
|
|
|
//Pass request to the controller for the follow-up.
|
|
jobStats, err := dh.controller.LaunchJob(jobReq)
|
|
if err != nil {
|
|
dh.handleError(w, http.StatusInternalServerError, errs.LaunchJobError(err))
|
|
return
|
|
}
|
|
|
|
data, ok := dh.handleJSONData(w, jobStats)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(http.StatusAccepted)
|
|
w.Write(data)
|
|
}
|
|
|
|
//HandleGetJobReq is implementation of method defined in interface 'Handler'
|
|
func (dh *DefaultHandler) HandleGetJobReq(w http.ResponseWriter, req *http.Request) {
|
|
if !dh.preCheck(w) {
|
|
return
|
|
}
|
|
|
|
vars := mux.Vars(req)
|
|
jobID := vars["job_id"]
|
|
|
|
jobStats, err := dh.controller.GetJob(jobID)
|
|
if err != nil {
|
|
code := http.StatusInternalServerError
|
|
backErr := errs.GetJobStatsError(err)
|
|
if errs.IsObjectNotFoundError(err) {
|
|
code = http.StatusNotFound
|
|
backErr = err
|
|
}
|
|
dh.handleError(w, code, backErr)
|
|
return
|
|
}
|
|
|
|
data, ok := dh.handleJSONData(w, jobStats)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write(data)
|
|
}
|
|
|
|
//HandleJobActionReq is implementation of method defined in interface 'Handler'
|
|
func (dh *DefaultHandler) HandleJobActionReq(w http.ResponseWriter, req *http.Request) {
|
|
if !dh.preCheck(w) {
|
|
return
|
|
}
|
|
|
|
vars := mux.Vars(req)
|
|
jobID := vars["job_id"]
|
|
|
|
data, err := ioutil.ReadAll(req.Body)
|
|
if err != nil {
|
|
dh.handleError(w, http.StatusInternalServerError, errs.ReadRequestBodyError(err))
|
|
return
|
|
}
|
|
|
|
//unmarshal data
|
|
jobActionReq := models.JobActionRequest{}
|
|
if err = json.Unmarshal(data, &jobActionReq); err != nil {
|
|
dh.handleError(w, http.StatusInternalServerError, errs.HandleJSONDataError(err))
|
|
return
|
|
}
|
|
|
|
switch jobActionReq.Action {
|
|
case opm.CtlCommandStop:
|
|
if err := dh.controller.StopJob(jobID); err != nil {
|
|
code := http.StatusInternalServerError
|
|
backErr := errs.StopJobError(err)
|
|
if errs.IsObjectNotFoundError(err) {
|
|
code = http.StatusNotFound
|
|
backErr = err
|
|
}
|
|
dh.handleError(w, code, backErr)
|
|
return
|
|
}
|
|
case opm.CtlCommandCancel:
|
|
if err := dh.controller.CancelJob(jobID); err != nil {
|
|
code := http.StatusInternalServerError
|
|
backErr := errs.CancelJobError(err)
|
|
if errs.IsObjectNotFoundError(err) {
|
|
code = http.StatusNotFound
|
|
backErr = err
|
|
}
|
|
dh.handleError(w, code, backErr)
|
|
return
|
|
}
|
|
case opm.CtlCommandRetry:
|
|
if err := dh.controller.RetryJob(jobID); err != nil {
|
|
code := http.StatusInternalServerError
|
|
backErr := errs.RetryJobError(err)
|
|
if errs.IsObjectNotFoundError(err) {
|
|
code = http.StatusNotFound
|
|
backErr = err
|
|
}
|
|
dh.handleError(w, code, backErr)
|
|
return
|
|
}
|
|
default:
|
|
dh.handleError(w, http.StatusNotImplemented, errs.UnknownActionNameError(fmt.Errorf("%s", jobID)))
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(http.StatusNoContent) //only header, no content returned
|
|
}
|
|
|
|
//HandleCheckStatusReq is implementation of method defined in interface 'Handler'
|
|
func (dh *DefaultHandler) HandleCheckStatusReq(w http.ResponseWriter, req *http.Request) {
|
|
if !dh.preCheck(w) {
|
|
return
|
|
}
|
|
|
|
stats, err := dh.controller.CheckStatus()
|
|
if err != nil {
|
|
dh.handleError(w, http.StatusInternalServerError, errs.CheckStatsError(err))
|
|
return
|
|
}
|
|
|
|
data, ok := dh.handleJSONData(w, stats)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write(data)
|
|
}
|
|
|
|
//HandleJobLogReq is implementation of method defined in interface 'Handler'
|
|
func (dh *DefaultHandler) HandleJobLogReq(w http.ResponseWriter, req *http.Request) {
|
|
if !dh.preCheck(w) {
|
|
return
|
|
}
|
|
|
|
vars := mux.Vars(req)
|
|
jobID := vars["job_id"]
|
|
|
|
if strings.Contains(jobID, "..") || strings.ContainsRune(jobID, os.PathSeparator) {
|
|
dh.handleError(w, http.StatusBadRequest, fmt.Errorf("Invalid Job ID: %s", jobID))
|
|
return
|
|
}
|
|
|
|
logData, err := dh.controller.GetJobLogData(jobID)
|
|
if err != nil {
|
|
code := http.StatusInternalServerError
|
|
backErr := errs.GetJobLogError(err)
|
|
if errs.IsObjectNotFoundError(err) {
|
|
code = http.StatusNotFound
|
|
backErr = err
|
|
}
|
|
dh.handleError(w, code, backErr)
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write(logData)
|
|
}
|
|
|
|
func (dh *DefaultHandler) handleJSONData(w http.ResponseWriter, object interface{}) ([]byte, bool) {
|
|
data, err := json.Marshal(object)
|
|
if err != nil {
|
|
dh.handleError(w, http.StatusInternalServerError, errs.HandleJSONDataError(err))
|
|
return nil, false
|
|
}
|
|
|
|
return data, true
|
|
}
|
|
|
|
func (dh *DefaultHandler) handleError(w http.ResponseWriter, code int, err error) {
|
|
w.WriteHeader(code)
|
|
w.Write([]byte(err.Error()))
|
|
}
|
|
|
|
func (dh *DefaultHandler) preCheck(w http.ResponseWriter) bool {
|
|
if dh.controller == nil {
|
|
dh.handleError(w, http.StatusInternalServerError, errs.MissingBackendHandlerError(fmt.Errorf("nil controller")))
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|