create different security context according to the rquest

This commit is contained in:
Wenkai Yin 2017-05-04 19:12:16 +08:00
parent b4c172b754
commit f8615e4746
10 changed files with 347 additions and 37 deletions

View File

@ -29,6 +29,9 @@ const (
RoleDeveloper = 3
RoleGuest = 4
DeployModeStandAlone = "standalone"
DeployModeIntegration = "integration"
ExtEndpoint = "ext_endpoint"
AUTHMode = "auth_mode"
DatabaseType = "database_type"

View File

@ -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,

View File

@ -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
}

View File

@ -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

View File

@ -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
}

View File

@ -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]
}

View File

@ -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{

View File

@ -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{},

View File

@ -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
}

View File

@ -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 {