enable secret validation on UI and let job service use this secret to communicate with uI

This commit is contained in:
Tan Jiang 2016-05-25 15:24:44 +08:00
parent 86e44363a7
commit 86112c942b
15 changed files with 127 additions and 61 deletions

View File

@ -6,15 +6,14 @@ import (
"github.com/vmware/harbor/api" "github.com/vmware/harbor/api"
"github.com/vmware/harbor/dao" "github.com/vmware/harbor/dao"
"github.com/vmware/harbor/job" "github.com/vmware/harbor/job"
"github.com/vmware/harbor/job/config"
"github.com/vmware/harbor/job/utils" "github.com/vmware/harbor/job/utils"
"github.com/vmware/harbor/models" "github.com/vmware/harbor/models"
"github.com/vmware/harbor/utils/log" "github.com/vmware/harbor/utils/log"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/http/httputil" "net/http/httputil"
"os"
"strconv" "strconv"
"strings"
) )
type ReplicationJob struct { type ReplicationJob struct {
@ -131,14 +130,7 @@ func (rj *ReplicationJob) GetLog() {
// calls the api from UI to get repo list // calls the api from UI to get repo list
func getRepoList(projectID int64) ([]string, error) { func getRepoList(projectID int64) ([]string, error) {
uiURL := os.Getenv("UI_URL") /*
if len(uiURL) == 0 {
uiURL = "ui"
}
if !strings.HasSuffix(uiURL, "/") {
uiURL += "/"
}
//TODO:Use secret key instead
uiUser := os.Getenv("UI_USR") uiUser := os.Getenv("UI_USR")
if len(uiUser) == 0 { if len(uiUser) == 0 {
uiUser = "admin" uiUser = "admin"
@ -147,13 +139,18 @@ func getRepoList(projectID int64) ([]string, error) {
if len(uiPwd) == 0 { if len(uiPwd) == 0 {
uiPwd = "Harbor12345" uiPwd = "Harbor12345"
} }
*/
uiURL := config.LocalHarborURL()
client := &http.Client{} client := &http.Client{}
req, err := http.NewRequest("GET", uiURL+"api/repositories?project_id="+strconv.Itoa(int(projectID)), nil) req, err := http.NewRequest("GET", uiURL+"api/repositories?project_id="+strconv.Itoa(int(projectID)), nil)
if err != nil { if err != nil {
log.Errorf("Error when creating request: %v") log.Errorf("Error when creating request: %v")
return nil, err return nil, err
} }
req.SetBasicAuth(uiUser, uiPwd) //req.SetBasicAuth(uiUser, uiPwd)
req.AddCookie(&http.Cookie{Name: models.UISecretCookie, Value: config.UISecret()})
//dump, err := httputil.DumpRequest(req, true)
//log.Debugf("req: %q", dump)
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
log.Errorf("Error when calling UI api to get repositories, error: %v", err) log.Errorf("Error when calling UI api to get repositories, error: %v", err)

View File

@ -26,6 +26,7 @@ import (
"github.com/docker/distribution/manifest/schema1" "github.com/docker/distribution/manifest/schema1"
"github.com/vmware/harbor/dao" "github.com/vmware/harbor/dao"
"github.com/vmware/harbor/models" "github.com/vmware/harbor/models"
"github.com/vmware/harbor/service/cache"
svc_utils "github.com/vmware/harbor/service/utils" svc_utils "github.com/vmware/harbor/service/utils"
"github.com/vmware/harbor/utils/log" "github.com/vmware/harbor/utils/log"
"github.com/vmware/harbor/utils/registry" "github.com/vmware/harbor/utils/registry"
@ -43,8 +44,12 @@ type RepositoryAPI struct {
// Prepare will set a non existent user ID in case the request tries to view repositories under a project he doesn't has permission. // Prepare will set a non existent user ID in case the request tries to view repositories under a project he doesn't has permission.
func (ra *RepositoryAPI) Prepare() { func (ra *RepositoryAPI) Prepare() {
if svc_utils.VerifySecret(ra.Ctx.Request) {
ra.userID = 1
} else {
ra.userID = ra.ValidateUser() ra.userID = ra.ValidateUser()
} }
}
// Get ... // Get ...
func (ra *RepositoryAPI) Get() { func (ra *RepositoryAPI) Get() {
@ -69,7 +74,7 @@ func (ra *RepositoryAPI) Get() {
return return
} }
repoList, err := svc_utils.GetRepoFromCache() repoList, err := cache.GetRepoFromCache()
if err != nil { if err != nil {
log.Errorf("Failed to get repo from cache, error: %v", err) log.Errorf("Failed to get repo from cache, error: %v", err)
ra.RenderError(http.StatusInternalServerError, "internal sever error") ra.RenderError(http.StatusInternalServerError, "internal sever error")
@ -145,7 +150,7 @@ func (ra *RepositoryAPI) Delete() {
go func() { go func() {
log.Debug("refreshing catalog cache") log.Debug("refreshing catalog cache")
if err := svc_utils.RefreshCatalogCache(); err != nil { if err := cache.RefreshCatalogCache(); err != nil {
log.Errorf("error occurred while refresh catalog cache: %v", err) log.Errorf("error occurred while refresh catalog cache: %v", err)
} }
}() }()

View File

@ -22,7 +22,7 @@ import (
"github.com/vmware/harbor/dao" "github.com/vmware/harbor/dao"
"github.com/vmware/harbor/models" "github.com/vmware/harbor/models"
svc_utils "github.com/vmware/harbor/service/utils" "github.com/vmware/harbor/service/cache"
"github.com/vmware/harbor/utils" "github.com/vmware/harbor/utils"
"github.com/vmware/harbor/utils/log" "github.com/vmware/harbor/utils/log"
) )
@ -85,7 +85,7 @@ func (s *SearchAPI) Get() {
} }
} }
repositories, err2 := svc_utils.GetRepoFromCache() repositories, err2 := cache.GetRepoFromCache()
if err2 != nil { if err2 != nil {
log.Errorf("Failed to get repos from cache, error: %v", err2) log.Errorf("Failed to get repos from cache, error: %v", err2)
s.CustomAbort(http.StatusInternalServerError, "Failed to get repositories search result") s.CustomAbort(http.StatusInternalServerError, "Failed to get repositories search result")

View File

@ -21,7 +21,7 @@ import (
"github.com/vmware/harbor/dao" "github.com/vmware/harbor/dao"
"github.com/vmware/harbor/models" "github.com/vmware/harbor/models"
svc_utils "github.com/vmware/harbor/service/utils" "github.com/vmware/harbor/service/cache"
"github.com/vmware/harbor/utils/log" "github.com/vmware/harbor/utils/log"
) )
@ -88,7 +88,7 @@ func (s *StatisticAPI) Get() {
//getReposByProject returns repo numbers of specified project //getReposByProject returns repo numbers of specified project
func getRepoCountByProject(projectName string) int { func getRepoCountByProject(projectName string) int {
repoList, err := svc_utils.GetRepoFromCache() repoList, err := cache.GetRepoFromCache()
if err != nil { if err != nil {
log.Errorf("Failed to get repo from cache, error: %v", err) log.Errorf("Failed to get repo from cache, error: %v", err)
return 0 return 0
@ -107,7 +107,7 @@ func getRepoCountByProject(projectName string) int {
//getTotalRepoCount returns total repo count //getTotalRepoCount returns total repo count
func getTotalRepoCount() int { func getTotalRepoCount() int {
repoList, err := svc_utils.GetRepoFromCache() repoList, err := cache.GetRepoFromCache()
if err != nil { if err != nil {
log.Errorf("Failed to get repo from cache, error: %v", err) log.Errorf("Failed to get repo from cache, error: %v", err)
return 0 return 0

View File

@ -11,8 +11,9 @@ import (
const defaultMaxWorkers int = 10 const defaultMaxWorkers int = 10
var maxJobWorkers int var maxJobWorkers int
var localRegURL string var localURL string
var logDir string var logDir string
var uiSecret string
func init() { func init() {
maxWorkersEnv := os.Getenv("MAX_JOB_WORKERS") maxWorkersEnv := os.Getenv("MAX_JOB_WORKERS")
@ -23,9 +24,9 @@ func init() {
maxJobWorkers = defaultMaxWorkers maxJobWorkers = defaultMaxWorkers
} }
localRegURL = os.Getenv("LOCAL_REGISTRY_URL") localURL = os.Getenv("LOCAL_HARBOR_URL")
if len(localRegURL) == 0 { if len(localURL) == 0 {
localRegURL = "http://registry:5000/" localURL = "http://ui/"
} }
logDir = os.Getenv("LOG_DIR") logDir = os.Getenv("LOG_DIR")
@ -46,19 +47,29 @@ func init() {
panic(fmt.Sprintf("%s is not a direcotry", logDir)) panic(fmt.Sprintf("%s is not a direcotry", logDir))
} }
uiSecret = os.Getenv("UI_SECRET")
if len(uiSecret) == 0 {
panic("UI Secret is not set")
}
log.Debugf("config: maxJobWorkers: %d", maxJobWorkers) log.Debugf("config: maxJobWorkers: %d", maxJobWorkers)
log.Debugf("config: localRegURL: %s", localRegURL) log.Debugf("config: localHarborURL: %s", localURL)
log.Debugf("config: logDir: %s", logDir) log.Debugf("config: logDir: %s", logDir)
log.Debugf("config: uiSecret: ******")
} }
func MaxJobWorkers() int { func MaxJobWorkers() int {
return maxJobWorkers return maxJobWorkers
} }
func LocalRegURL() string { func LocalHarborURL() string {
return localRegURL return localURL
} }
func LogDir() string { func LogDir() string {
return logDir return logDir
} }
func UISecret() string {
return uiSecret
}

View File

@ -50,11 +50,10 @@ type BaseHandler struct {
tags []string tags []string
srcURL string // url of source registry srcURL string // url of source registry
srcSecretKey string // secretKey ...
dstURL string // url of target registry dstURL string // url of target registry
dstUsr string // username ... dstUsr string // username ...
dstPwd string // password ... dstPwd string // username ...
srcClient *registry.Repository srcClient *registry.Repository
dstClient *registry.Repository dstClient *registry.Repository
@ -69,7 +68,7 @@ type BaseHandler struct {
// InitBaseHandler initializes a BaseHandler: creating clients for source and destination registry, // InitBaseHandler initializes a BaseHandler: creating clients for source and destination registry,
// listing tags of the repository if parameter tags is nil. // listing tags of the repository if parameter tags is nil.
func InitBaseHandler(repository, srcURL, srcSecretKey, func InitBaseHandler(repository, srcURL, srcSecret,
dstURL, dstUsr, dstPwd string, tags []string, logger *log.Logger) (*BaseHandler, error) { dstURL, dstUsr, dstPwd string, tags []string, logger *log.Logger) (*BaseHandler, error) {
logger.Infof("initializing: repository: %s, tags: %v, source URL: %s, destination URL: %s, destination user: %s", logger.Infof("initializing: repository: %s, tags: %v, source URL: %s, destination URL: %s, destination user: %s",
@ -79,7 +78,6 @@ func InitBaseHandler(repository, srcURL, srcSecretKey,
repository: repository, repository: repository,
tags: tags, tags: tags,
srcURL: srcURL, srcURL: srcURL,
srcSecretKey: srcSecretKey,
dstURL: dstURL, dstURL: dstURL,
dstUsr: dstUsr, dstUsr: dstUsr,
dstPwd: dstPwd, dstPwd: dstPwd,
@ -89,8 +87,9 @@ func InitBaseHandler(repository, srcURL, srcSecretKey,
base.project = getProjectName(base.repository) base.project = getProjectName(base.repository)
//TODO using secret key c := &http.Cookie{Name: models.UISecretCookie, Value: srcSecret}
srcCred := auth.NewBasicAuthCredential("admin", "Harbor12345") srcCred := auth.NewCookieCredential(c)
// srcCred := auth.NewBasicAuthCredential("admin", "Harbor12345")
srcClient, err := registry.NewRepositoryWithCredential(base.repository, base.srcURL, srcCred) srcClient, err := registry.NewRepositoryWithCredential(base.repository, base.srcURL, srcCred)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -180,7 +180,7 @@ func (sm *JobSM) Reset(jid int64) error {
return fmt.Errorf("The policy doesn't exist in DB, policy id:%d", job.PolicyID) return fmt.Errorf("The policy doesn't exist in DB, policy id:%d", job.PolicyID)
} }
sm.Parms = &RepJobParm{ sm.Parms = &RepJobParm{
LocalRegURL: config.LocalRegURL(), LocalRegURL: config.LocalHarborURL(),
Repository: job.Repository, Repository: job.Repository,
Enabled: policy.Enabled, Enabled: policy.Enabled,
Operation: job.Operation, Operation: job.Operation,
@ -224,7 +224,7 @@ func (sm *JobSM) Reset(jid int64) error {
} }
func addImgOutTransition(sm *JobSM) error { func addImgOutTransition(sm *JobSM) error {
base, err := replication.InitBaseHandler(sm.Parms.Repository, sm.Parms.LocalRegURL, "", base, err := replication.InitBaseHandler(sm.Parms.Repository, sm.Parms.LocalRegURL, config.UISecret(),
sm.Parms.TargetURL, sm.Parms.TargetUsername, sm.Parms.TargetPassword, sm.Parms.TargetURL, sm.Parms.TargetUsername, sm.Parms.TargetPassword,
nil, sm.Logger) nil, sm.Logger)
if err != nil { if err != nil {

View File

@ -3,9 +3,10 @@ export MYSQL_PORT=3306
export MYSQL_USR=root export MYSQL_USR=root
export MYSQL_PWD=root123 export MYSQL_PWD=root123
export LOG_LEVEL=debug export LOG_LEVEL=debug
export UI_URL=http://127.0.0.1/ export LOCAL_HARBOR_URL=http://127.0.0.1/
export UI_USR=admin export UI_SECRET=abcdef
export UI_PWD=Harbor12345 #export UI_USR=admin
#export UI_PWD=Harbor12345
export MAX_JOB_WORKERS=1 export MAX_JOB_WORKERS=1
./jobservice ./jobservice

View File

@ -1,3 +1,3 @@
use registry; use registry;
insert into replication_target (name, url, username, password) values ('test', '192.168.0.2:5000', 'testuser', 'passw0rd'); insert into replication_target (name, url, username, password) values ('test', 'http://10.117.171.31', 'admin', 'Harbor12345');
insert into replication_policy (name, project_id, target_id, enabled, start_time) value ('test_policy', 1, 1, 1, NOW()); insert into replication_policy (name, project_id, target_id, enabled, start_time) value ('test_policy', 1, 1, 1, NOW());

View File

@ -15,6 +15,8 @@ const (
JobContinue string = "_continue" JobContinue string = "_continue"
RepOpTransfer string = "transfer" RepOpTransfer string = "transfer"
RepOpDelete string = "delete" RepOpDelete string = "delete"
//cookie name to contain the UI secret
UISecretCookie string = "uisecret"
) )
type RepPolicy struct { type RepPolicy struct {

View File

@ -13,7 +13,7 @@
limitations under the License. limitations under the License.
*/ */
package utils package cache
import ( import (
"os" "os"

View File

@ -22,7 +22,7 @@ import (
"github.com/vmware/harbor/dao" "github.com/vmware/harbor/dao"
"github.com/vmware/harbor/models" "github.com/vmware/harbor/models"
svc_utils "github.com/vmware/harbor/service/utils" "github.com/vmware/harbor/service/cache"
"github.com/vmware/harbor/utils/log" "github.com/vmware/harbor/utils/log"
"github.com/astaxie/beego" "github.com/astaxie/beego"
@ -70,7 +70,7 @@ func (n *NotificationHandler) Post() {
go dao.AccessLog(username, project, repo, repoTag, action) go dao.AccessLog(username, project, repo, repoTag, action)
if action == "push" { if action == "push" {
go func() { go func() {
err2 := svc_utils.RefreshCatalogCache() err2 := cache.RefreshCatalogCache()
if err2 != nil { if err2 != nil {
log.Errorf("Error happens when refreshing cache: %v", err2) log.Errorf("Error happens when refreshing cache: %v", err2)
} }

View File

@ -21,7 +21,7 @@ import (
"github.com/vmware/harbor/auth" "github.com/vmware/harbor/auth"
"github.com/vmware/harbor/models" "github.com/vmware/harbor/models"
//svc_utils "github.com/vmware/harbor/service/utils" svc_utils "github.com/vmware/harbor/service/utils"
"github.com/vmware/harbor/utils/log" "github.com/vmware/harbor/utils/log"
"github.com/astaxie/beego" "github.com/astaxie/beego"
@ -38,21 +38,28 @@ type Handler struct {
// checkes the permission agains local DB and generates jwt token. // checkes the permission agains local DB and generates jwt token.
func (h *Handler) Get() { func (h *Handler) Get() {
var username string
request := h.Ctx.Request request := h.Ctx.Request
log.Infof("request url: %v", request.URL.String())
username, password, _ := request.BasicAuth()
authenticated := authenticate(username, password)
service := h.GetString("service") service := h.GetString("service")
scopes := h.GetStrings("scope") scopes := h.GetStrings("scope")
access := GetResourceActions(scopes)
log.Infof("request url: %v", request.URL.String())
if svc_utils.VerifySecret(request) {
log.Debugf("Will grant all access as this request is from job service with legal secret.")
username = "job-service-user"
} else {
username, password, _ := request.BasicAuth()
authenticated := authenticate(username, password)
if len(scopes) == 0 && !authenticated { if len(scopes) == 0 && !authenticated {
log.Info("login request with invalid credentials") log.Info("login request with invalid credentials")
h.CustomAbort(http.StatusUnauthorized, "") h.CustomAbort(http.StatusUnauthorized, "")
} }
access := GetResourceActions(scopes)
for _, a := range access { for _, a := range access {
FilterAccess(username, authenticated, a) FilterAccess(username, authenticated, a)
} }
}
h.serveToken(username, service, access) h.serveToken(username, service, access)
} }

30
service/utils/utils.go Normal file
View File

@ -0,0 +1,30 @@
/*
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package utils
import (
"github.com/vmware/harbor/utils/log"
"net/http"
"os"
)
func VerifySecret(r *http.Request) bool {
secret := os.Getenv("UI_SECRET")
c, err := r.Cookie("uisecret")
if err != nil {
log.Errorf("Failed to get secret cookie, error: %v", err)
}
return c != nil && c.Value == secret
}

View File

@ -42,3 +42,17 @@ func NewBasicAuthCredential(username, password string) Credential {
func (b *basicAuthCredential) AddAuthorization(req *http.Request) { func (b *basicAuthCredential) AddAuthorization(req *http.Request) {
req.SetBasicAuth(b.username, b.password) req.SetBasicAuth(b.username, b.password)
} }
type cookieCredential struct {
cookie *http.Cookie
}
func NewCookieCredential(c *http.Cookie) Credential {
return &cookieCredential{
cookie: c,
}
}
func (c *cookieCredential) AddAuthorization(req *http.Request) {
req.AddCookie(c.cookie)
}