Introduce "compute:connect" role
This commit is contained in:
parent
688238837a
commit
5d64ff08b8
|
|
@ -18,6 +18,7 @@ import (
|
|||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-openapi/runtime/middleware"
|
||||
"github.com/penglongli/gin-metrics/ginmetrics"
|
||||
"github.com/samber/lo"
|
||||
"go.uber.org/zap"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
|
@ -239,9 +240,31 @@ func (controller *Controller) authenticateMiddleware(c *gin.Context) {
|
|||
c.Next()
|
||||
}
|
||||
|
||||
type AuthorizeMode int
|
||||
|
||||
const (
|
||||
AuthorizeModeAll AuthorizeMode = iota
|
||||
AuthorizeModeAny
|
||||
)
|
||||
|
||||
func (controller *Controller) authorize(
|
||||
ctx *gin.Context,
|
||||
requiredRoles ...v1pkg.ServiceAccountRole,
|
||||
) responder.Responder {
|
||||
return controller.authorizeBase(ctx, AuthorizeModeAll, requiredRoles...)
|
||||
}
|
||||
|
||||
func (controller *Controller) authorizeAny(
|
||||
ctx *gin.Context,
|
||||
requiredRoles ...v1pkg.ServiceAccountRole,
|
||||
) responder.Responder {
|
||||
return controller.authorizeBase(ctx, AuthorizeModeAny, requiredRoles...)
|
||||
}
|
||||
|
||||
func (controller *Controller) authorizeBase(
|
||||
ctx *gin.Context,
|
||||
mode AuthorizeMode,
|
||||
requiredRoles ...v1pkg.ServiceAccountRole,
|
||||
) responder.Responder {
|
||||
if controller.insecureAuthDisabled {
|
||||
return nil
|
||||
|
|
@ -254,21 +277,34 @@ func (controller *Controller) authorize(
|
|||
serviceAccount := serviceAccountUntyped.(*v1pkg.ServiceAccount)
|
||||
serviceAccountRolesSet := mapset.NewSet[v1pkg.ServiceAccountRole](serviceAccount.Roles...)
|
||||
|
||||
requiredRolesSet := mapset.NewSet[v1pkg.ServiceAccountRole](requiredRoles...)
|
||||
var authorized bool
|
||||
|
||||
missingRoles := requiredRolesSet.Difference(serviceAccountRolesSet).ToSlice()
|
||||
if len(missingRoles) == 0 {
|
||||
switch mode {
|
||||
case AuthorizeModeAll:
|
||||
authorized = serviceAccountRolesSet.Contains(requiredRoles...)
|
||||
case AuthorizeModeAny:
|
||||
authorized = serviceAccountRolesSet.ContainsAny(requiredRoles...)
|
||||
}
|
||||
|
||||
if authorized {
|
||||
return nil
|
||||
}
|
||||
|
||||
var missingRolesStrings []string
|
||||
var hint string
|
||||
|
||||
for _, missingRole := range missingRoles {
|
||||
missingRolesStrings = append(missingRolesStrings, string(missingRole))
|
||||
switch mode {
|
||||
case AuthorizeModeAll:
|
||||
hint = "all of the following roles must be present"
|
||||
case AuthorizeModeAny:
|
||||
hint = "any of the following roles must be present"
|
||||
}
|
||||
|
||||
humanizedRoles := lo.Map(requiredRoles, func(role v1pkg.ServiceAccountRole, _ int) string {
|
||||
return string(role)
|
||||
})
|
||||
|
||||
return responder.JSON(http.StatusUnauthorized,
|
||||
NewErrorResponse("missing roles: %s", strings.Join(missingRolesStrings, ", ")))
|
||||
NewErrorResponse("%s: %s", hint, strings.Join(humanizedRoles, ", ")))
|
||||
}
|
||||
|
||||
func (controller *Controller) authorizeGRPC(ctx context.Context, scopes ...v1pkg.ServiceAccountRole) bool {
|
||||
|
|
|
|||
|
|
@ -3,18 +3,20 @@ package controller
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/cirruslabs/orchard/internal/responder"
|
||||
v1 "github.com/cirruslabs/orchard/pkg/resource/v1"
|
||||
"github.com/cirruslabs/orchard/rpc"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (controller *Controller) ip(ctx *gin.Context) responder.Responder {
|
||||
if responder := controller.authorize(ctx, v1.ServiceAccountRoleComputeWrite); responder != nil {
|
||||
if responder := controller.authorizeAny(ctx, v1.ServiceAccountRoleComputeWrite,
|
||||
v1.ServiceAccountRoleComputeConnect); responder != nil {
|
||||
return responder
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,10 @@ package controller
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
storepkg "github.com/cirruslabs/orchard/internal/controller/store"
|
||||
"github.com/cirruslabs/orchard/internal/netconncancel"
|
||||
"github.com/cirruslabs/orchard/internal/proxy"
|
||||
|
|
@ -15,13 +19,11 @@ import (
|
|||
"github.com/pkg/errors"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (controller *Controller) portForwardVM(ctx *gin.Context) responder.Responder {
|
||||
if responder := controller.authorize(ctx, v1.ServiceAccountRoleComputeWrite); responder != nil {
|
||||
if responder := controller.authorizeAny(ctx, v1.ServiceAccountRoleComputeWrite,
|
||||
v1.ServiceAccountRoleComputeConnect); responder != nil {
|
||||
return responder
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,10 @@ import (
|
|||
"crypto/subtle"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cirruslabs/orchard/internal/controller/notifier"
|
||||
"github.com/cirruslabs/orchard/internal/controller/rendezvous"
|
||||
storepkg "github.com/cirruslabs/orchard/internal/controller/store"
|
||||
|
|
@ -15,9 +19,6 @@ import (
|
|||
"github.com/samber/lo"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -110,9 +111,12 @@ func (server *SSHServer) passwordCallback(connMetadata ssh.ConnMetadata, passwor
|
|||
}
|
||||
|
||||
// Authorize
|
||||
if !lo.Contains(serviceAccount.Roles, v1.ServiceAccountRoleComputeWrite) {
|
||||
return fmt.Errorf("authorization failed for user %q because it lacks %q role",
|
||||
connMetadata.User(), v1.ServiceAccountRoleComputeWrite)
|
||||
authorized := lo.Contains(serviceAccount.Roles, v1.ServiceAccountRoleComputeWrite) ||
|
||||
lo.Contains(serviceAccount.Roles, v1.ServiceAccountRoleComputeConnect)
|
||||
|
||||
if !authorized {
|
||||
return fmt.Errorf("authorization failed for user %q because it lacks %q or %q roles",
|
||||
connMetadata.User(), v1.ServiceAccountRoleComputeWrite, v1.ServiceAccountRoleComputeConnect)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -10,10 +10,11 @@ var ErrUnsupportedServiceAccountRole = errors.New("unsupported service account r
|
|||
type ServiceAccountRole string
|
||||
|
||||
const (
|
||||
ServiceAccountRoleComputeRead ServiceAccountRole = "compute:read"
|
||||
ServiceAccountRoleComputeWrite ServiceAccountRole = "compute:write"
|
||||
ServiceAccountRoleAdminRead ServiceAccountRole = "admin:read"
|
||||
ServiceAccountRoleAdminWrite ServiceAccountRole = "admin:write"
|
||||
ServiceAccountRoleComputeRead ServiceAccountRole = "compute:read"
|
||||
ServiceAccountRoleComputeWrite ServiceAccountRole = "compute:write"
|
||||
ServiceAccountRoleComputeConnect ServiceAccountRole = "compute:connect"
|
||||
ServiceAccountRoleAdminRead ServiceAccountRole = "admin:read"
|
||||
ServiceAccountRoleAdminWrite ServiceAccountRole = "admin:write"
|
||||
)
|
||||
|
||||
func NewServiceAccountRole(name string) (ServiceAccountRole, error) {
|
||||
|
|
@ -22,6 +23,8 @@ func NewServiceAccountRole(name string) (ServiceAccountRole, error) {
|
|||
return ServiceAccountRoleComputeRead, nil
|
||||
case string(ServiceAccountRoleComputeWrite):
|
||||
return ServiceAccountRoleComputeWrite, nil
|
||||
case string(ServiceAccountRoleComputeConnect):
|
||||
return ServiceAccountRoleComputeConnect, nil
|
||||
case string(ServiceAccountRoleAdminRead):
|
||||
return ServiceAccountRoleAdminRead, nil
|
||||
case string(ServiceAccountRoleAdminWrite):
|
||||
|
|
@ -35,6 +38,7 @@ func AllServiceAccountRoles() []ServiceAccountRole {
|
|||
return []ServiceAccountRole{
|
||||
ServiceAccountRoleComputeRead,
|
||||
ServiceAccountRoleComputeWrite,
|
||||
ServiceAccountRoleComputeConnect,
|
||||
ServiceAccountRoleAdminRead,
|
||||
ServiceAccountRoleAdminWrite,
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue