mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-22 18:25:56 +01:00
Merge pull request #4595 from vmware/enable_auth_checking
Apply auth checking to all the incoming requests
This commit is contained in:
commit
2b799a576b
62
src/jobservice/api/authenticator.go
Normal file
62
src/jobservice/api/authenticator.go
Normal 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
|
||||
}
|
@ -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) {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user