Merge pull request #4595 from vmware/enable_auth_checking

Apply auth checking to all the incoming requests
This commit is contained in:
Steven Zou 2018-04-09 12:50:34 +08:00 committed by GitHub
commit 2b799a576b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 143 additions and 5 deletions

View File

@ -0,0 +1,62 @@
// Copyright 2018 The Harbor Authors. All rights reserved.
package api
import (
"errors"
"fmt"
"net/http"
"strings"
"github.com/vmware/harbor/src/jobservice/config"
"github.com/vmware/harbor/src/jobservice/utils"
)
const (
secretPrefix = "Harbor-Secret"
authHeader = "Authorization"
)
//Authenticator defined behaviors of doing auth checking.
type Authenticator interface {
//Auth incoming request
//
//req *http.Request: the incoming request
//
//Returns:
// nil returned if successfully done
// otherwise an error returned
DoAuth(req *http.Request) error
}
//SecretAuthenticator implements interface 'Authenticator' based on simple secret.
type SecretAuthenticator struct{}
//DoAuth implements same method in interface 'Authenticator'.
func (sa *SecretAuthenticator) DoAuth(req *http.Request) error {
if req == nil {
return errors.New("nil request")
}
h := strings.TrimSpace(req.Header.Get(authHeader))
if utils.IsEmptyStr(h) {
return fmt.Errorf("header '%s' missing", authHeader)
}
if !strings.HasPrefix(h, secretPrefix) {
return fmt.Errorf("'%s' should start with '%s' but got '%s' now", authHeader, secretPrefix, h)
}
secret := strings.TrimSpace(strings.TrimPrefix(h, secretPrefix))
//incase both two are empty
if utils.IsEmptyStr(secret) {
return errors.New("empty secret is not allowed")
}
expectedSecret := config.GetUIAuthSecret()
if expectedSecret != secret {
return errors.New("unauthorized")
}
return nil
}

View File

@ -9,6 +9,7 @@ import (
"io/ioutil"
"math/rand"
"net/http"
"os"
"strings"
"sync"
"testing"
@ -18,8 +19,11 @@ import (
"github.com/vmware/harbor/src/jobservice/models"
)
const fakeSecret = "I'mfakesecret"
var testingAuthProvider = &SecretAuthenticator{}
var testingHandler = NewDefaultHandler(&fakeController{})
var testingRouter = NewBaseRouter(testingHandler)
var testingRouter = NewBaseRouter(testingHandler, testingAuthProvider)
var client = &http.Client{
Timeout: 10 * time.Second,
Transport: &http.Transport{
@ -28,7 +32,28 @@ var client = &http.Client{
},
}
func TestUnAuthorizedAccess(t *testing.T) {
exportUISecret("hello")
server, port, ctx := createServer()
server.Start()
<-time.After(200 * time.Millisecond)
res, err := getReq(fmt.Sprintf("http://localhost:%d/api/v1/jobs/fake_job", port))
if e := expectFormatedError(res, err); e != nil {
t.Fatal(e)
}
if strings.Index(err.Error(), "401") == -1 {
t.Fatalf("expect '401' but got none 401 error")
}
server.Stop()
ctx.WG.Wait()
}
func TestLaunchJobFailed(t *testing.T) {
exportUISecret(fakeSecret)
server, port, ctx := createServer()
server.Start()
<-time.After(200 * time.Millisecond)
@ -43,6 +68,8 @@ func TestLaunchJobFailed(t *testing.T) {
}
func TestLaunchJobSucceed(t *testing.T) {
exportUISecret(fakeSecret)
server, port, ctx := createServer()
server.Start()
<-time.After(200 * time.Millisecond)
@ -64,6 +91,8 @@ func TestLaunchJobSucceed(t *testing.T) {
}
func TestGetJobFailed(t *testing.T) {
exportUISecret(fakeSecret)
server, port, ctx := createServer()
server.Start()
<-time.After(200 * time.Millisecond)
@ -78,6 +107,8 @@ func TestGetJobFailed(t *testing.T) {
}
func TestGetJobSucceed(t *testing.T) {
exportUISecret(fakeSecret)
server, port, ctx := createServer()
server.Start()
<-time.After(200 * time.Millisecond)
@ -99,6 +130,8 @@ func TestGetJobSucceed(t *testing.T) {
}
func TestJobActionFailed(t *testing.T) {
exportUISecret(fakeSecret)
server, port, ctx := createServer()
server.Start()
<-time.After(200 * time.Millisecond)
@ -129,6 +162,8 @@ func TestJobActionFailed(t *testing.T) {
}
func TestJobActionSucceed(t *testing.T) {
exportUISecret(fakeSecret)
server, port, ctx := createServer()
server.Start()
<-time.After(200 * time.Millisecond)
@ -165,6 +200,8 @@ func TestJobActionSucceed(t *testing.T) {
}
func TestCheckStatus(t *testing.T) {
exportUISecret(fakeSecret)
server, port, ctx := createServer()
server.Start()
<-time.After(200 * time.Millisecond)
@ -191,6 +228,8 @@ func TestCheckStatus(t *testing.T) {
}
func TestGetJobLog(t *testing.T) {
exportUISecret(fakeSecret)
server, port, ctx := createServer()
server.Start()
<-time.After(200 * time.Millisecond)
@ -269,6 +308,8 @@ func postReq(url string, data []byte) ([]byte, error) {
return nil, err
}
req.Header.Set(authHeader, fmt.Sprintf("%s %s", secretPrefix, fakeSecret))
res, err := client.Do(req)
if err != nil {
return nil, err
@ -299,6 +340,8 @@ func getReq(url string) ([]byte, error) {
return nil, err
}
req.Header.Set(authHeader, fmt.Sprintf("%s %s", secretPrefix, fakeSecret))
res, err := client.Do(req)
if err != nil {
return nil, err
@ -317,6 +360,10 @@ func getReq(url string) ([]byte, error) {
return data, nil
}
func exportUISecret(secret string) {
os.Setenv("UI_SECRET", secret)
}
type fakeController struct{}
func (fc *fakeController) LaunchJob(req models.JobRequest) (models.JobStats, error) {

View File

@ -7,6 +7,7 @@ import (
"net/http"
"github.com/gorilla/mux"
"github.com/vmware/harbor/src/jobservice/errs"
)
const (
@ -28,13 +29,17 @@ type BaseRouter struct {
//Handler used to handle the requests
handler Handler
//Do auth
authenticator Authenticator
}
//NewBaseRouter is the constructor of BaseRouter.
func NewBaseRouter(handler Handler) Router {
func NewBaseRouter(handler Handler, authenticator Authenticator) Router {
br := &BaseRouter{
router: mux.NewRouter(),
handler: handler,
router: mux.NewRouter(),
handler: handler,
authenticator: authenticator,
}
//Register routes here
@ -45,6 +50,14 @@ func NewBaseRouter(handler Handler) Router {
//ServeHTTP is the implementation of Router interface.
func (br *BaseRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
//Do auth
if err := br.authenticator.DoAuth(req); err != nil {
authErr := errs.UnauthorizedError(err)
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte(authErr.Error()))
return
}
//Directly pass requests to the server mux.
br.router.ServeHTTP(w, req)
}

View File

@ -38,6 +38,9 @@ const (
//JobServicePoolBackendRedis represents redis backend
JobServicePoolBackendRedis = "redis"
//secret of UI
uiAuthSecret = "UI_SECRET"
)
//DefaultConfig is the default configuration reference
@ -151,6 +154,11 @@ func GetAuthSecret() string {
return utils.ReadEnv(jobServiceAuthSecret)
}
//GetUIAuthSecret get the auth secret of UI side
func GetUIAuthSecret() string {
return utils.ReadEnv(uiAuthSecret)
}
//GetAdminServerEndpoint return the admin server endpoint
func GetAdminServerEndpoint() string {
return DefaultConfig.AdminServer

View File

@ -36,6 +36,8 @@ const (
GetJobLogErrorCode
//NoObjectFoundErrorCode is code for the error of no object found
NoObjectFoundErrorCode
//UnAuthorizedErrorCode is code for the error of unauthorized accessing
UnAuthorizedErrorCode
)
//baseError ...
@ -118,6 +120,11 @@ func GetJobLogError(err error) error {
return New(GetJobLogErrorCode, "Failed to get the job log", err.Error())
}
//UnauthorizedError is error for the case of unauthorized accessing
func UnauthorizedError(err error) error {
return New(UnAuthorizedErrorCode, "Unauthorized", err.Error())
}
//jobStoppedError is designed for the case of stopping job.
type jobStoppedError struct {
baseError

View File

@ -125,8 +125,9 @@ func (bs *Bootstrap) LoadAndRun() {
//Load and run the API server.
func (bs *Bootstrap) loadAndRunAPIServer(ctx *env.Context, cfg *config.Configuration, ctl *core.Controller) *api.Server {
//Initialized API server
authProvider := &api.SecretAuthenticator{}
handler := api.NewDefaultHandler(ctl)
router := api.NewBaseRouter(handler)
router := api.NewBaseRouter(handler, authProvider)
serverConfig := api.ServerConfig{
Protocol: cfg.Protocol,
Port: cfg.Port,