Merge pull request #9285 from heww/cherry-pick-1.8.0-9280

[Cherry pick]fix(robot): robot account improvement
This commit is contained in:
Wang Yan 2019-09-27 14:05:44 +08:00 committed by GitHub
commit dea5ba4754
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 131 additions and 15 deletions

View File

@ -110,6 +110,10 @@ func (p *Policy) GetEffect() string {
return eft.String() return eft.String()
} }
func (p *Policy) String() string {
return p.Resource.String() + ":" + p.Action.String() + ":" + p.GetEffect()
}
// Role the interface of rbac role // Role the interface of rbac role
type Role interface { type Role interface {
// GetRoleName returns the role identity, if empty string role's policies will be ignore // GetRoleName returns the role identity, if empty string role's policies will be ignore

View File

@ -9,7 +9,7 @@ import (
type robot struct { type robot struct {
username string username string
namespace rbac.Namespace namespace rbac.Namespace
policy []*rbac.Policy policies []*rbac.Policy
} }
// GetUserName get the robot name. // GetUserName get the robot name.
@ -23,7 +23,7 @@ func (r *robot) GetPolicies() []*rbac.Policy {
if r.namespace.IsPublic() { if r.namespace.IsPublic() {
policies = append(policies, project.PoliciesForPublicProject(r.namespace)...) policies = append(policies, project.PoliciesForPublicProject(r.namespace)...)
} }
policies = append(policies, r.policy...) policies = append(policies, r.policies...)
return policies return policies
} }
@ -33,10 +33,30 @@ func (r *robot) GetRoles() []rbac.Role {
} }
// NewRobot ... // NewRobot ...
func NewRobot(username string, namespace rbac.Namespace, policy []*rbac.Policy) rbac.User { func NewRobot(username string, namespace rbac.Namespace, policies []*rbac.Policy) rbac.User {
return &robot{ return &robot{
username: username, username: username,
namespace: namespace, namespace: namespace,
policy: policy, policies: filterPolicies(namespace, policies),
} }
} }
func filterPolicies(namespace rbac.Namespace, policies []*rbac.Policy) []*rbac.Policy {
var results []*rbac.Policy
if len(policies) == 0 {
return results
}
mp := map[string]bool{}
for _, policy := range project.GetAllPolicies(namespace) {
mp[policy.String()] = true
}
for _, policy := range policies {
if mp[policy.String()] {
results = append(results, policy)
}
}
return results
}

View File

@ -33,10 +33,21 @@ func TestGetPolicies(t *testing.T) {
robot := robot{ robot := robot{
username: "test", username: "test",
namespace: rbac.NewProjectNamespace(1, false), namespace: rbac.NewProjectNamespace(1, false),
policy: policies, policies: policies,
} }
assert.Equal(t, robot.GetUserName(), "test") assert.Equal(t, robot.GetUserName(), "test")
assert.NotNil(t, robot.GetPolicies()) assert.NotNil(t, robot.GetPolicies())
assert.Nil(t, robot.GetRoles()) assert.Nil(t, robot.GetRoles())
} }
func TestNewRobot(t *testing.T) {
policies := []*rbac.Policy{
{Resource: "/project/1/repository", Action: "pull"},
{Resource: "/project/library/repository", Action: "pull"},
{Resource: "/project/library/repository", Action: "push"},
}
robot := NewRobot("test", rbac.NewProjectNamespace(1, false), policies)
assert.Len(t, robot.GetPolicies(), 1)
}

View File

@ -25,6 +25,7 @@ import (
"github.com/goharbor/harbor/src/common/dao" "github.com/goharbor/harbor/src/common/dao"
"github.com/goharbor/harbor/src/common/models" "github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/common/rbac" "github.com/goharbor/harbor/src/common/rbac"
"github.com/goharbor/harbor/src/common/rbac/project"
"github.com/goharbor/harbor/src/common/token" "github.com/goharbor/harbor/src/common/token"
"github.com/goharbor/harbor/src/core/config" "github.com/goharbor/harbor/src/core/config"
) )
@ -106,6 +107,11 @@ func (r *RobotAPI) Post() {
return return
} }
if err := validateRobotReq(r.project, &robotReq); err != nil {
r.SendBadRequestError(err)
return
}
// Token duration in minutes // Token duration in minutes
tokenDuration := time.Duration(config.RobotTokenDuration()) * time.Minute tokenDuration := time.Duration(config.RobotTokenDuration()) * time.Minute
expiresAt := time.Now().UTC().Add(tokenDuration).Unix() expiresAt := time.Now().UTC().Add(tokenDuration).Unix()
@ -251,3 +257,25 @@ func (r *RobotAPI) Delete() {
return return
} }
} }
func validateRobotReq(p *models.Project, robotReq *models.RobotReq) error {
if len(robotReq.Access) == 0 {
return errors.New("access required")
}
namespace, _ := rbac.Resource(fmt.Sprintf("/project/%d", p.ProjectID)).GetNamespace()
policies := project.GetAllPolicies(namespace)
mp := map[string]bool{}
for _, policy := range policies {
mp[policy.String()] = true
}
for _, policy := range robotReq.Access {
if !mp[policy.String()] {
return fmt.Errorf("%s action of %s resource not exist in project %s", policy.Action, policy.Resource, p.Name)
}
}
return nil
}

View File

@ -16,10 +16,11 @@ package api
import ( import (
"fmt" "fmt"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/common/rbac"
"net/http" "net/http"
"testing" "testing"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/common/rbac"
) )
var ( var (
@ -28,9 +29,10 @@ var (
) )
func TestRobotAPIPost(t *testing.T) { func TestRobotAPIPost(t *testing.T) {
res := rbac.Resource("/project/1")
rbacPolicy := &rbac.Policy{ rbacPolicy := &rbac.Policy{
Resource: "/project/libray/repository", Resource: res.Subresource(rbac.ResourceRepository),
Action: "pull", Action: "pull",
} }
policies := []*rbac.Policy{} policies := []*rbac.Policy{}
@ -70,6 +72,64 @@ func TestRobotAPIPost(t *testing.T) {
}, },
code: http.StatusCreated, code: http.StatusCreated,
}, },
// 400
{
request: &testingRequest{
method: http.MethodPost,
url: robotPath,
bodyJSON: &models.RobotReq{
Name: "testIllgel#",
Description: "test desc",
},
credential: projAdmin4Robot,
},
code: http.StatusBadRequest,
},
{
request: &testingRequest{
method: http.MethodPost,
url: robotPath,
bodyJSON: &models.RobotReq{
Name: "test",
Description: "resource not exist",
Access: []*rbac.Policy{
{Resource: res.Subresource("foo"), Action: rbac.ActionCreate},
},
},
credential: projAdmin4Robot,
},
code: http.StatusBadRequest,
},
{
request: &testingRequest{
method: http.MethodPost,
url: robotPath,
bodyJSON: &models.RobotReq{
Name: "test",
Description: "action not exist",
Access: []*rbac.Policy{
{Resource: res.Subresource(rbac.ResourceRepository), Action: "foo"},
},
},
credential: projAdmin4Robot,
},
code: http.StatusBadRequest,
},
{
request: &testingRequest{
method: http.MethodPost,
url: robotPath,
bodyJSON: &models.RobotReq{
Name: "test",
Description: "policy not exit",
Access: []*rbac.Policy{
{Resource: res.Subresource(rbac.ResourceMember), Action: rbac.ActionPush},
},
},
credential: projAdmin4Robot,
},
code: http.StatusBadRequest,
},
// 403 -- developer // 403 -- developer
{ {
request: &testingRequest{ request: &testingRequest{

View File

@ -27,11 +27,9 @@ export class RobotService {
let access = []; let access = [];
if ( isPull ) { if ( isPull ) {
access.push({"resource": "/project/" + projecId + "/repository", "action": "pull"}); access.push({"resource": "/project/" + projecId + "/repository", "action": "pull"});
access.push({"resource": "/project/" + projectName + "/repository", "action": "pull"});
} }
if ( isPush ) { if ( isPush ) {
access.push({"resource": "/project/" + projecId + "/repository", "action": "push"}); access.push({"resource": "/project/" + projecId + "/repository", "action": "push"});
access.push({"resource": "/project/" + projectName + "/repository", "action": "push"});
} }
let param = { let param = {

View File

@ -192,19 +192,14 @@ class Project(base.Base):
has_pull_right = True has_pull_right = True
access_list = [] access_list = []
resource_by_project_id = "/project/"+str(project_id)+"/repository" resource_by_project_id = "/project/"+str(project_id)+"/repository"
resource_by_project_name = "/project/"+project_name+"/repository"
action_pull = "pull" action_pull = "pull"
action_push = "push" action_push = "push"
if has_pull_right is True: if has_pull_right is True:
robotAccountAccess = swagger_client.RobotAccountAccess(resource = resource_by_project_id, action = action_pull) robotAccountAccess = swagger_client.RobotAccountAccess(resource = resource_by_project_id, action = action_pull)
access_list.append(robotAccountAccess) access_list.append(robotAccountAccess)
robotAccountAccess = swagger_client.RobotAccountAccess(resource = resource_by_project_name, action = action_pull)
access_list.append(robotAccountAccess)
if has_push_right is True: if has_push_right is True:
robotAccountAccess = swagger_client.RobotAccountAccess(resource = resource_by_project_id, action = action_push) robotAccountAccess = swagger_client.RobotAccountAccess(resource = resource_by_project_id, action = action_push)
access_list.append(robotAccountAccess) access_list.append(robotAccountAccess)
robotAccountAccess = swagger_client.RobotAccountAccess(resource = resource_by_project_name, action = action_push)
access_list.append(robotAccountAccess)
robotAccountCreate = swagger_client.RobotAccountCreate(robot_name, robot_desc, access_list) robotAccountCreate = swagger_client.RobotAccountCreate(robot_name, robot_desc, access_list)
client = self._get_client(**kwargs) client = self._get_client(**kwargs)
data = [] data = []