mirror of
https://github.com/goharbor/harbor
synced 2025-04-15 21:43:40 +00:00
create different security context according to the rquest
This commit is contained in:
parent
b4c172b754
commit
f8615e4746
|
@ -29,6 +29,9 @@ const (
|
|||
RoleDeveloper = 3
|
||||
RoleGuest = 4
|
||||
|
||||
DeployModeStandAlone = "standalone"
|
||||
DeployModeIntegration = "integration"
|
||||
|
||||
ExtEndpoint = "ext_endpoint"
|
||||
AUTHMode = "auth_mode"
|
||||
DatabaseType = "database_type"
|
||||
|
|
|
@ -17,17 +17,17 @@ package rbac
|
|||
import (
|
||||
"github.com/vmware/harbor/src/common"
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
"github.com/vmware/harbor/src/ui/pm"
|
||||
"github.com/vmware/harbor/src/ui/projectmanager"
|
||||
)
|
||||
|
||||
// SecurityContext implements security.Context interface based on database
|
||||
type SecurityContext struct {
|
||||
user *models.User
|
||||
pm pm.PM
|
||||
pm projectmanager.ProjectManager
|
||||
}
|
||||
|
||||
// NewSecurityContext ...
|
||||
func NewSecurityContext(user *models.User, pm pm.PM) *SecurityContext {
|
||||
func NewSecurityContext(user *models.User, pm projectmanager.ProjectManager) *SecurityContext {
|
||||
return &SecurityContext{
|
||||
user: user,
|
||||
pm: pm,
|
||||
|
|
|
@ -24,7 +24,10 @@ import (
|
|||
"github.com/vmware/harbor/src/common"
|
||||
comcfg "github.com/vmware/harbor/src/common/config"
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
"github.com/vmware/harbor/src/common/secret"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
"github.com/vmware/harbor/src/ui/projectmanager"
|
||||
"github.com/vmware/harbor/src/ui/projectmanager/db"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -33,10 +36,15 @@ const (
|
|||
)
|
||||
|
||||
var (
|
||||
// SecretStore manages secrets
|
||||
SecretStore *secret.Store
|
||||
// AdminserverClient is a client for adminserver
|
||||
AdminserverClient client.Client
|
||||
mg *comcfg.Manager
|
||||
keyProvider comcfg.KeyProvider
|
||||
// DBProjectManager is the project manager based on database,
|
||||
// it is initialized only the deploy mode is standalone
|
||||
DBProjectManager projectmanager.ProjectManager
|
||||
mg *comcfg.Manager
|
||||
keyProvider comcfg.KeyProvider
|
||||
)
|
||||
|
||||
// Init configurations
|
||||
|
@ -62,6 +70,12 @@ func Init() error {
|
|||
return err
|
||||
}
|
||||
|
||||
// init secret store
|
||||
initSecretStore()
|
||||
|
||||
// init project manager based on database
|
||||
initDBProjectManager()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -75,6 +89,20 @@ func initKeyProvider() {
|
|||
keyProvider = comcfg.NewFileKeyProvider(path)
|
||||
}
|
||||
|
||||
func initSecretStore() {
|
||||
m := map[string]string{}
|
||||
m[secret.JobserviceUser] = JobserviceSecret()
|
||||
SecretStore = secret.NewStore(m)
|
||||
}
|
||||
|
||||
func initDBProjectManager() {
|
||||
if len(DeployMode()) == 0 ||
|
||||
DeployMode() == common.DeployModeStandAlone {
|
||||
log.Info("initializing the project manager based on database...")
|
||||
DBProjectManager = &db.ProjectManager{}
|
||||
}
|
||||
}
|
||||
|
||||
// Load configurations
|
||||
func Load() error {
|
||||
_, err := mg.Load()
|
||||
|
@ -271,6 +299,7 @@ func UISecret() string {
|
|||
|
||||
// JobserviceSecret returns a secret to mark Jobservice when communicate with
|
||||
// other component
|
||||
// TODO replace it with method of SecretStore
|
||||
func JobserviceSecret() string {
|
||||
return os.Getenv("JOBSERVICE_SECRET")
|
||||
}
|
||||
|
@ -303,3 +332,9 @@ func AdmiralEndpoint() string {
|
|||
func WithAdmiral() bool {
|
||||
return len(AdmiralEndpoint()) > 0
|
||||
}
|
||||
|
||||
// DeployMode returns the deploy mode
|
||||
// TODO read from adminserver
|
||||
func DeployMode() string {
|
||||
return common.DeployModeStandAlone
|
||||
}
|
||||
|
|
|
@ -71,6 +71,7 @@ func (cc *CommonController) Login() {
|
|||
|
||||
cc.SetSession("userId", user.UserID)
|
||||
cc.SetSession("username", user.Username)
|
||||
cc.SetSession("isSysAdmin", user.HasAdminRole == 1)
|
||||
}
|
||||
|
||||
// LogOut Habor UI
|
||||
|
|
|
@ -15,22 +15,29 @@
|
|||
package filter
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/astaxie/beego/context"
|
||||
"github.com/vmware/harbor/src/common/security"
|
||||
beegoctx "github.com/astaxie/beego/context"
|
||||
"github.com/vmware/harbor/src/common"
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
"github.com/vmware/harbor/src/common/security/rbac"
|
||||
"github.com/vmware/harbor/src/common/security/secret"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
"github.com/vmware/harbor/src/ui/auth"
|
||||
"github.com/vmware/harbor/src/ui/config"
|
||||
"github.com/vmware/harbor/src/ui/projectmanager"
|
||||
)
|
||||
|
||||
const (
|
||||
// HarborSecurityContext is the name of security context passed to handlers
|
||||
HarborSecurityContext = "harbor_security_context"
|
||||
// HarborProjectManager is the name of project manager passed to handlers
|
||||
HarborProjectManager = "harbor_project_manager"
|
||||
)
|
||||
|
||||
// SecurityFilter authenticates the request and passes a security context with it
|
||||
// which can be used to do some authorization
|
||||
func SecurityFilter(ctx *context.Context) {
|
||||
func SecurityFilter(ctx *beegoctx.Context) {
|
||||
if ctx == nil {
|
||||
return
|
||||
}
|
||||
|
@ -40,20 +47,89 @@ func SecurityFilter(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(req.RequestURI, "/api/") &&
|
||||
!strings.HasPrefix(req.RequestURI, "/service/") {
|
||||
if !strings.HasPrefix(req.URL.RequestURI(), "/api/") &&
|
||||
!strings.HasPrefix(req.URL.RequestURI(), "/service/") {
|
||||
return
|
||||
}
|
||||
|
||||
securityCtx, err := createSecurityContext(req)
|
||||
if err != nil {
|
||||
log.Warningf("failed to create security context: %v", err)
|
||||
// fill ctx with security context and project manager
|
||||
fillContext(ctx)
|
||||
}
|
||||
|
||||
func fillContext(ctx *beegoctx.Context) {
|
||||
// secret
|
||||
scrt := ctx.GetCookie("secret")
|
||||
if len(scrt) != 0 {
|
||||
ctx.Input.SetData(HarborProjectManager,
|
||||
getProjectManager(ctx))
|
||||
|
||||
log.Info("creating a secret security context...")
|
||||
ctx.Input.SetData(HarborSecurityContext,
|
||||
secret.NewSecurityContext(scrt, config.SecretStore))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Input.SetData(HarborSecurityContext, securityCtx)
|
||||
var user *models.User
|
||||
var err error
|
||||
|
||||
// basic auth
|
||||
username, password, ok := ctx.Request.BasicAuth()
|
||||
if ok {
|
||||
// TODO the return data contains other params when integrated
|
||||
// with vic
|
||||
user, err = auth.Login(models.AuthModel{
|
||||
Principal: username,
|
||||
Password: password,
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("failed to authenticate %s: %v", username, err)
|
||||
}
|
||||
if user != nil {
|
||||
log.Info("got user information via basic auth")
|
||||
}
|
||||
}
|
||||
|
||||
// session
|
||||
if user == nil {
|
||||
username := ctx.Input.Session("username")
|
||||
isSysAdmin := ctx.Input.Session("isSysAdmin")
|
||||
if username != nil {
|
||||
user = &models.User{
|
||||
Username: username.(string),
|
||||
}
|
||||
|
||||
if isSysAdmin != nil && isSysAdmin.(bool) {
|
||||
user.HasAdminRole = 1
|
||||
}
|
||||
log.Info("got user information from session")
|
||||
}
|
||||
|
||||
// TODO maybe need to get token from session
|
||||
}
|
||||
|
||||
if user == nil {
|
||||
log.Info("user information is nil")
|
||||
}
|
||||
|
||||
pm := getProjectManager(ctx)
|
||||
ctx.Input.SetData(HarborProjectManager, pm)
|
||||
|
||||
log.Info("creating a rbac security context...")
|
||||
ctx.Input.SetData(HarborSecurityContext,
|
||||
rbac.NewSecurityContext(user, pm))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func createSecurityContext(req *http.Request) (security.Context, error) {
|
||||
return nil, nil
|
||||
func getProjectManager(ctx *beegoctx.Context) projectmanager.ProjectManager {
|
||||
if len(config.DeployMode()) == 0 ||
|
||||
config.DeployMode() == common.DeployModeStandAlone {
|
||||
log.Info("filling a project manager based on database...")
|
||||
return config.DBProjectManager
|
||||
}
|
||||
|
||||
// TODO create project manager based on pms
|
||||
log.Info("filling a project manager based on pms...")
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -15,22 +15,73 @@
|
|||
package filter
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/astaxie/beego/context"
|
||||
"github.com/astaxie/beego/session"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/vmware/harbor/src/common/dao"
|
||||
"github.com/vmware/harbor/src/common/security"
|
||||
"github.com/vmware/harbor/src/common/security/rbac"
|
||||
"github.com/vmware/harbor/src/common/security/secret"
|
||||
_ "github.com/vmware/harbor/src/ui/auth/db"
|
||||
_ "github.com/vmware/harbor/src/ui/auth/ldap"
|
||||
"github.com/vmware/harbor/src/ui/config"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
// initialize beego session manager
|
||||
conf := map[string]interface{}{
|
||||
"cookieName": beego.BConfig.WebConfig.Session.SessionName,
|
||||
"gclifetime": beego.BConfig.WebConfig.Session.SessionGCMaxLifetime,
|
||||
"providerConfig": filepath.ToSlash(beego.BConfig.WebConfig.Session.SessionProviderConfig),
|
||||
"secure": beego.BConfig.Listen.EnableHTTPS,
|
||||
"enableSetCookie": beego.BConfig.WebConfig.Session.SessionAutoSetCookie,
|
||||
"domain": beego.BConfig.WebConfig.Session.SessionDomain,
|
||||
"cookieLifeTime": beego.BConfig.WebConfig.Session.SessionCookieLifeTime,
|
||||
}
|
||||
confBytes, err := json.Marshal(conf)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to marshal session conf: %v", err)
|
||||
}
|
||||
|
||||
beego.GlobalSessions, err = session.NewManager("memory", string(confBytes))
|
||||
if err != nil {
|
||||
log.Fatalf("failed to create session manager: %v", err)
|
||||
}
|
||||
|
||||
if err := config.Init(); err != nil {
|
||||
log.Fatalf("failed to initialize configurations: %v", err)
|
||||
}
|
||||
database, err := config.Database()
|
||||
if err != nil {
|
||||
log.Fatalf("failed to get database configurations: %v", err)
|
||||
}
|
||||
if err = dao.InitDatabase(database); err != nil {
|
||||
log.Fatalf("failed to initialize database: %v", err)
|
||||
}
|
||||
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestSecurityFilter(t *testing.T) {
|
||||
// nil request
|
||||
ctx := &context.Context{
|
||||
Request: nil,
|
||||
Input: context.NewInput(),
|
||||
ctx, err := newContext(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to crate context: %v", err)
|
||||
}
|
||||
SecurityFilter(ctx)
|
||||
securityContext := ctx.Input.GetData(HarborSecurityContext)
|
||||
assert.Nil(t, securityContext)
|
||||
assert.Nil(t, securityContext(ctx))
|
||||
assert.Nil(t, projectManager(ctx))
|
||||
|
||||
// the pattern of request does not need security check
|
||||
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1/static/index.html", nil)
|
||||
|
@ -38,13 +89,156 @@ func TestSecurityFilter(t *testing.T) {
|
|||
t.Fatalf("failed to create request: %v", req)
|
||||
}
|
||||
|
||||
ctx = &context.Context{
|
||||
Request: req,
|
||||
Input: context.NewInput(),
|
||||
ctx, err = newContext(req)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to crate context: %v", err)
|
||||
}
|
||||
SecurityFilter(ctx)
|
||||
securityContext = ctx.Input.GetData(HarborSecurityContext)
|
||||
assert.Nil(t, securityContext)
|
||||
assert.Nil(t, securityContext(ctx))
|
||||
assert.Nil(t, projectManager(ctx))
|
||||
|
||||
//TODO add a case to test normal process
|
||||
// the pattern of request needs security check
|
||||
req, err = http.NewRequest(http.MethodGet,
|
||||
"http://127.0.0.1/api/projects/", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create request: %v", req)
|
||||
}
|
||||
|
||||
ctx, err = newContext(req)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to crate context: %v", err)
|
||||
}
|
||||
SecurityFilter(ctx)
|
||||
assert.NotNil(t, securityContext(ctx))
|
||||
assert.NotNil(t, projectManager(ctx))
|
||||
}
|
||||
|
||||
func TestFillContext(t *testing.T) {
|
||||
// secret
|
||||
req, err := http.NewRequest(http.MethodGet,
|
||||
"http://127.0.0.1/api/projects/", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create request: %v", req)
|
||||
}
|
||||
req.AddCookie(&http.Cookie{
|
||||
Name: "secret",
|
||||
Value: "secret",
|
||||
})
|
||||
|
||||
ctx, err := newContext(req)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to crate context: %v", err)
|
||||
}
|
||||
|
||||
fillContext(ctx)
|
||||
assert.IsType(t, &secret.SecurityContext{},
|
||||
securityContext(ctx))
|
||||
assert.NotNil(t, projectManager(ctx))
|
||||
|
||||
// session
|
||||
req, err = http.NewRequest(http.MethodGet,
|
||||
"http://127.0.0.1/api/projects/", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create request: %v", req)
|
||||
}
|
||||
store, err := beego.GlobalSessions.SessionStart(httptest.NewRecorder(), req)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create session store: %v", err)
|
||||
}
|
||||
if err = store.Set("username", "admin"); err != nil {
|
||||
t.Fatalf("failed to set session: %v", err)
|
||||
}
|
||||
if err = store.Set("isSysAdmin", true); err != nil {
|
||||
t.Fatalf("failed to set session: %v", err)
|
||||
}
|
||||
|
||||
req, err = http.NewRequest(http.MethodGet,
|
||||
"http://127.0.0.1/api/projects/", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create request: %v", req)
|
||||
}
|
||||
addSessionIDToCookie(req, store.SessionID())
|
||||
|
||||
ctx, err = newContext(req)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to crate context: %v", err)
|
||||
}
|
||||
fillContext(ctx)
|
||||
sc := securityContext(ctx)
|
||||
assert.IsType(t, &rbac.SecurityContext{}, sc)
|
||||
s := sc.(security.Context)
|
||||
assert.Equal(t, "admin", s.GetUsername())
|
||||
assert.True(t, s.IsSysAdmin())
|
||||
assert.NotNil(t, projectManager(ctx))
|
||||
|
||||
// basic auth
|
||||
req, err = http.NewRequest(http.MethodGet,
|
||||
"http://127.0.0.1/api/projects/", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create request: %v", req)
|
||||
}
|
||||
req.SetBasicAuth("admin", "Harbor12345")
|
||||
|
||||
ctx, err = newContext(req)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to crate context: %v", err)
|
||||
}
|
||||
fillContext(ctx)
|
||||
sc = securityContext(ctx)
|
||||
assert.IsType(t, &rbac.SecurityContext{}, sc)
|
||||
s = sc.(security.Context)
|
||||
assert.Equal(t, "admin", s.GetUsername())
|
||||
assert.NotNil(t, projectManager(ctx))
|
||||
|
||||
// no credential
|
||||
req, err = http.NewRequest(http.MethodGet,
|
||||
"http://127.0.0.1/api/projects/", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create request: %v", req)
|
||||
}
|
||||
|
||||
ctx, err = newContext(req)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to crate context: %v", err)
|
||||
}
|
||||
fillContext(ctx)
|
||||
sc = securityContext(ctx)
|
||||
assert.IsType(t, &rbac.SecurityContext{}, sc)
|
||||
s = sc.(security.Context)
|
||||
assert.False(t, s.IsAuthenticated())
|
||||
assert.NotNil(t, projectManager(ctx))
|
||||
}
|
||||
|
||||
func newContext(req *http.Request) (*context.Context, error) {
|
||||
var err error
|
||||
ctx := context.NewContext()
|
||||
ctx.Reset(httptest.NewRecorder(), req)
|
||||
if req != nil {
|
||||
ctx.Input.CruSession, err = beego.GlobalSessions.SessionStart(ctx.ResponseWriter, req)
|
||||
}
|
||||
return ctx, err
|
||||
}
|
||||
|
||||
func addSessionIDToCookie(req *http.Request, sessionID string) {
|
||||
cookie := &http.Cookie{
|
||||
Name: beego.BConfig.WebConfig.Session.SessionName,
|
||||
Value: url.QueryEscape(sessionID),
|
||||
Path: "/",
|
||||
HttpOnly: true,
|
||||
Secure: beego.BConfig.Listen.EnableHTTPS,
|
||||
Domain: beego.BConfig.WebConfig.Session.SessionDomain,
|
||||
}
|
||||
cookie.MaxAge = beego.BConfig.WebConfig.Session.SessionCookieLifeTime
|
||||
cookie.Expires = time.Now().Add(
|
||||
time.Duration(
|
||||
beego.BConfig.WebConfig.Session.SessionCookieLifeTime) * time.Second)
|
||||
req.AddCookie(cookie)
|
||||
}
|
||||
|
||||
func securityContext(ctx *context.Context) interface{} {
|
||||
return ctx.Input.Data()[HarborSecurityContext]
|
||||
}
|
||||
|
||||
func projectManager(ctx *context.Context) interface{} {
|
||||
return ctx.Input.Data()[HarborProjectManager]
|
||||
}
|
||||
|
|
|
@ -21,11 +21,11 @@ import (
|
|||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
)
|
||||
|
||||
// PM implements pm.PM interface based on database
|
||||
type PM struct{}
|
||||
// ProjectManager implements pm.PM interface based on database
|
||||
type ProjectManager struct{}
|
||||
|
||||
// IsPublic returns whether the project is public or not
|
||||
func (p *PM) IsPublic(projectIDOrName interface{}) bool {
|
||||
func (p *ProjectManager) IsPublic(projectIDOrName interface{}) bool {
|
||||
var project *models.Project
|
||||
var err error
|
||||
switch projectIDOrName.(type) {
|
||||
|
@ -53,7 +53,7 @@ func (p *PM) IsPublic(projectIDOrName interface{}) bool {
|
|||
}
|
||||
|
||||
// GetRoles return a role list which contains the user's roles to the project
|
||||
func (p *PM) GetRoles(username string, projectIDOrName interface{}) []int {
|
||||
func (p *ProjectManager) GetRoles(username string, projectIDOrName interface{}) []int {
|
||||
roles := []int{}
|
||||
|
||||
user, err := dao.GetUser(models.User{
|
|
@ -71,7 +71,7 @@ func TestMain(m *testing.M) {
|
|||
}
|
||||
|
||||
func TestIsPublic(t *testing.T) {
|
||||
pms := &PM{}
|
||||
pms := &ProjectManager{}
|
||||
// project name
|
||||
assert.True(t, pms.IsPublic("library"))
|
||||
// project ID
|
||||
|
@ -83,7 +83,7 @@ func TestIsPublic(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestGetRoles(t *testing.T) {
|
||||
pm := &PM{}
|
||||
pm := &ProjectManager{}
|
||||
|
||||
// non exist user
|
||||
assert.Equal(t, []int{},
|
|
@ -12,11 +12,11 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pm
|
||||
package projectmanager
|
||||
|
||||
// PM is the project mamager which abstracts the operations related
|
||||
// ProjectManager is the project mamager which abstracts the operations related
|
||||
// to projects
|
||||
type PM interface {
|
||||
type ProjectManager interface {
|
||||
IsPublic(projectIDOrName interface{}) bool
|
||||
GetRoles(username string, projectIDOrName interface{}) []int
|
||||
}
|
|
@ -22,6 +22,7 @@ import (
|
|||
)
|
||||
|
||||
// VerifySecret verifies the UI_SECRET cookie in a http request.
|
||||
// TODO remove
|
||||
func VerifySecret(r *http.Request, expectedSecret string) bool {
|
||||
c, err := r.Cookie("secret")
|
||||
if err != nil {
|
||||
|
|
Loading…
Reference in New Issue
Block a user