权限整改:证书工作流补齐部署执行权限
证书申请任务和证书导入接口同时校验 certificate.manage 与 deployment.execute,补充权限拒绝回归测试并保持项目资源权限边界。
This commit is contained in:
parent
07f86f1413
commit
e514391da7
@ -142,7 +142,7 @@ func (s *Server) deployCertificateCurrent(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (s *Server) importCertificateFiles(c *gin.Context) {
|
||||
if !s.authorizeVisibleDomainPermission(c, domain.PermissionCertificateManage) {
|
||||
if !s.authorizeVisibleDomainPermissions(c, domain.PermissionCertificateManage, domain.PermissionDeploymentExecute) {
|
||||
return
|
||||
}
|
||||
if s.importCertificate == nil {
|
||||
|
||||
@ -405,3 +405,42 @@ func TestCertificateMutationAPIsEnforceProjectPermissions(t *testing.T) {
|
||||
t.Fatalf("certificate workflow was called %d times after authorization failure", calls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCertificateImportRequiresDeploymentPermission(t *testing.T) {
|
||||
repo := &stubDomainRepository{
|
||||
loaded: domain.Domain{
|
||||
ID: "domain-1",
|
||||
ResourceScope: domain.ResourceScope{ProjectID: domain.DefaultProjectID},
|
||||
},
|
||||
}
|
||||
calls := 0
|
||||
server := New(Dependencies{
|
||||
Config: readyTestConfig(t.TempDir()),
|
||||
Repository: repo,
|
||||
ProjectResources: projectResourceReaderStub{domains: repo},
|
||||
ProjectDomains: projectDomainRepositoryStub{
|
||||
DomainRepository: repo,
|
||||
authorize: func(_ context.Context, _ string, _ string, permission domain.ProjectPermission) error {
|
||||
if permission == domain.PermissionDeploymentExecute {
|
||||
return repository.ErrProjectAccessDenied
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
ImportCertificate: func(context.Context, certificate.ImportRequest) (certificate.IssueResult, deployment.Report, error) {
|
||||
calls++
|
||||
return certificate.IssueResult{}, deployment.Report{}, nil
|
||||
},
|
||||
})
|
||||
|
||||
request := httptest.NewRequest(http.MethodPost, "/api/v1/domains/domain-1/certificate/import", strings.NewReader(`{"certificate_path":"/ssl/cert.pem","private_key_path":"/ssl/key.pem"}`))
|
||||
request.Header.Set("Content-Type", "application/json")
|
||||
response := httptest.NewRecorder()
|
||||
serveAdminHTTP(server, response, request)
|
||||
if response.Code != http.StatusForbidden {
|
||||
t.Fatalf("缺少部署权限时证书导入未拒绝:%d %s", response.Code, response.Body.String())
|
||||
}
|
||||
if calls != 0 {
|
||||
t.Fatalf("权限拒绝后仍调用证书导入工作流:%d", calls)
|
||||
}
|
||||
}
|
||||
|
||||
@ -59,6 +59,22 @@ func (s *Server) authorizeVisibleDomainPermission(c *gin.Context, permission dom
|
||||
s.writeProjectResourceError(c, err)
|
||||
return false
|
||||
}
|
||||
return s.authorizeProjectPermissions(c, item.ProjectID, permission)
|
||||
}
|
||||
|
||||
func (s *Server) authorizeVisibleDomainPermissions(c *gin.Context, permissions ...domain.ProjectPermission) bool {
|
||||
if !s.requireProjectResourceReader(c) {
|
||||
return false
|
||||
}
|
||||
item, err := s.projectResources.GetVisibleDomain(c.Request.Context(), c.GetString(authenticatedActorKey), c.Param("id"))
|
||||
if err != nil {
|
||||
s.writeProjectResourceError(c, err)
|
||||
return false
|
||||
}
|
||||
return s.authorizeProjectPermissions(c, item.ProjectID, permissions...)
|
||||
}
|
||||
|
||||
func (s *Server) authorizeProjectPermissions(c *gin.Context, projectID string, permissions ...domain.ProjectPermission) bool {
|
||||
authorizer, ok := s.projectDomains.(repository.ProjectAuthorizer)
|
||||
if !ok {
|
||||
if fallback, fallbackOK := s.projectMembers.(repository.ProjectAuthorizer); fallbackOK {
|
||||
@ -70,9 +86,11 @@ func (s *Server) authorizeVisibleDomainPermission(c *gin.Context, permission dom
|
||||
s.writeProjectResourceError(c, errors.New("project authorizer unavailable"))
|
||||
return false
|
||||
}
|
||||
if err := authorizer.AuthorizeProject(c.Request.Context(), c.GetString(authenticatedActorKey), item.ProjectID, permission); err != nil {
|
||||
s.writeProjectResourceError(c, err)
|
||||
return false
|
||||
for _, permission := range permissions {
|
||||
if err := authorizer.AuthorizeProject(c.Request.Context(), c.GetString(authenticatedActorKey), projectID, permission); err != nil {
|
||||
s.writeProjectResourceError(c, err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -52,6 +52,14 @@ func (s *Server) issueDomainAsTask(c *gin.Context) {
|
||||
s.writeError(c, http.StatusInternalServerError, "检查证书任务权限失败", err)
|
||||
return
|
||||
}
|
||||
if err := authorizer.AuthorizeProject(c.Request.Context(), c.GetString(authenticatedActorKey), resource.ResourceScope.Normalize().ProjectID, domain.PermissionDeploymentExecute); err != nil {
|
||||
if errors.Is(err, repository.ErrProjectAccessDenied) {
|
||||
s.writeError(c, http.StatusForbidden, "无权执行此项目的证书部署任务", nil)
|
||||
return
|
||||
}
|
||||
s.writeError(c, http.StatusInternalServerError, "检查证书部署权限失败", err)
|
||||
return
|
||||
}
|
||||
if !s.authorizeDNSCredentialUse(c, resource.ResourceScope.Normalize().ProjectID) {
|
||||
return
|
||||
}
|
||||
|
||||
@ -120,6 +120,43 @@ func TestTaskAPIRecordsSuccessfulIssueAndAudit(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssueTaskRequiresDeploymentPermission(t *testing.T) {
|
||||
calls := 0
|
||||
server, _, _, closeDB := newTaskAPITestServer(t, func(context.Context, string) (certificate.IssueResult, deployment.Report, error) {
|
||||
calls++
|
||||
return certificate.IssueResult{}, deployment.Report{}, nil
|
||||
})
|
||||
defer closeDB()
|
||||
|
||||
server.projectMembers = permissionProjectMemberRepository{
|
||||
ProjectMemberRepository: server.projectMembers,
|
||||
allow: func(permission domain.ProjectPermission) error {
|
||||
if permission == domain.PermissionDeploymentExecute {
|
||||
return repository.ErrProjectAccessDenied
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
response := httptest.NewRecorder()
|
||||
serveAdminHTTP(server, response, httptest.NewRequest(http.MethodPost, "/api/v1/domains/domain-1/issue", nil))
|
||||
if response.Code != http.StatusForbidden {
|
||||
t.Fatalf("缺少部署权限时证书任务未拒绝:%d %s", response.Code, response.Body.String())
|
||||
}
|
||||
if calls != 0 {
|
||||
t.Fatalf("权限拒绝后仍调用证书申请工作流:%d", calls)
|
||||
}
|
||||
}
|
||||
|
||||
type permissionProjectMemberRepository struct {
|
||||
repository.ProjectMemberRepository
|
||||
allow func(domain.ProjectPermission) error
|
||||
}
|
||||
|
||||
func (p permissionProjectMemberRepository) AuthorizeProject(_ context.Context, _ string, _ string, permission domain.ProjectPermission) error {
|
||||
return p.allow(permission)
|
||||
}
|
||||
|
||||
type failingTaskStartRepository struct {
|
||||
repository.TaskRepository
|
||||
}
|
||||
|
||||
@ -16,9 +16,13 @@ import (
|
||||
type projectDomainRepositoryStub struct {
|
||||
repository.DomainRepository
|
||||
authorizeErr error
|
||||
authorize func(context.Context, string, string, domain.ProjectPermission) error
|
||||
}
|
||||
|
||||
func (r projectDomainRepositoryStub) AuthorizeProject(context.Context, string, string, domain.ProjectPermission) error {
|
||||
func (r projectDomainRepositoryStub) AuthorizeProject(ctx context.Context, actorID, projectID string, permission domain.ProjectPermission) error {
|
||||
if r.authorize != nil {
|
||||
return r.authorize(ctx, actorID, projectID, permission)
|
||||
}
|
||||
return r.authorizeErr
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user