Skip to content

Commit

Permalink
NYDB-165 iam auth plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
bma13 committed Aug 16, 2024
1 parent 35c0f72 commit 5f40514
Show file tree
Hide file tree
Showing 35 changed files with 3,082 additions and 386 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
cmd/ydbcp/ydbcp
plugins/auth_nebius/auth_nebius.so
25 changes: 21 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
FILES := $(filter-out ./pkg/proto/%, $(shell find . -type f -name '*.go'))
PACKAGES ?= $(filter-out ydbcp/pkg/proto/%, $(shell go list ./...))
FILES := $(shell find . -type f -name '*.go' ! -path '*/proto/*' )
PACKAGES := $(foreach path,$(shell go list ./...),$(if $(findstring /proto/,$(path)),,$(path)))
RELEASE_DIR := $(shell pwd)

$(info $$FILES = $(FILES))
$(info $$PACKAGES = $(PACKAGES))
$(info $$RELEASE_DIR = $(RELEASE_DIR))

.PHONY: all

all: test fmt lint proto build

proto:
$(MAKE) -C pkg/proto
$(MAKE) -C plugins/auth_nebius/proto

test:
go test -v ./... -short
Expand All @@ -19,6 +25,17 @@ fmt:
lint:
golint $(PACKAGES)

build: ydbcp
ydbcp:
build: build-plugins build-ydbcp
build-ydbcp:
go build -C cmd/ydbcp -o ydbcp

build-plugins: build-auth_nebius
build-auth_nebius:
go build -C plugins/auth_nebius -buildmode=plugin -o auth_nebius.so

clean:
rm -f plugins/auth_nebius/auth_nebius.so cmd/ydbcp/ydbcp

clean-proto:
find pkg/proto -type f -name '*.go' -delete
find plugins/auth_nebius/proto -type f -name '*.go' -delete
102 changes: 98 additions & 4 deletions cmd/ydbcp/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ import (
_ "go.uber.org/automaxprocs"
"go.uber.org/zap"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/reflection"

"ydbcp/internal/auth"
"ydbcp/internal/config"
configInit "ydbcp/internal/config"
"ydbcp/internal/connectors/client"
Expand All @@ -29,11 +31,14 @@ import (
"ydbcp/internal/processor"
"ydbcp/internal/types"
"ydbcp/internal/util/xlog"
ap "ydbcp/pkg/plugins/auth"
pb "ydbcp/pkg/proto/ydbcp/v1alpha1"
)

var (
port = flag.Int("port", 50051, "The server port")
port = flag.Int("port", 50051, "The server port")
errPermissionDenied = errors.New("permission denied")
errGetAuthToken = errors.New("can't get auth token")
)

// server is used to implement BackupService.
Expand All @@ -43,6 +48,57 @@ type server struct {
driver db.DBConnector
clientConn client.ClientConnector
s3 config.S3Config
auth ap.AuthProvider
}

func tokenFromContext(ctx context.Context) (string, error) {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return "", errGetAuthToken
}
tokens, ok := md["authorization"]
if !ok {
return "", fmt.Errorf("can't find authorization header, %w", errGetAuthToken)
}
if len(tokens) == 0 {
return "", fmt.Errorf("incorrect authorization header format, %w", errGetAuthToken)
}
token := tokens[0]
if len(token) < 8 || token[0:7] != "Bearer " {
return "", fmt.Errorf("incorrect authorization header format, %w", errGetAuthToken)
}
token = token[7:]
return token, nil
}

func (s *server) checkAuth(ctx context.Context, permission, containerID, resourceID string) (string, error) {
token, err := tokenFromContext(ctx)
if err != nil {
xlog.Debug(ctx, "can't get auth token", zap.Error(err))
token = ""
}
check := ap.AuthorizeCheck{
Permission: permission,
ContainerID: containerID,
}
if len(resourceID) > 0 {
check.ResourceID = []string{resourceID}
}

resp, subject, err := s.auth.Authorize(ctx, token, check)
if err != nil {
xlog.Error(ctx, "auth plugin authorize error", zap.Error(err))
return "", errPermissionDenied
}
if len(resp) != 1 {
xlog.Error(ctx, "incorrect auth plugin response length != 1")
return "", errPermissionDenied
}
if resp[0].Code != ap.AuthCodeSuccess {
xlog.Error(ctx, "auth plugin response", zap.Int("code", int(resp[0].Code)), zap.String("message", resp[0].Message))
return "", errPermissionDenied
}
return subject, nil
}

func (s *server) GetBackup(ctx context.Context, request *pb.GetBackupRequest) (*pb.Backup, error) {
Expand All @@ -68,14 +124,24 @@ func (s *server) GetBackup(ctx context.Context, request *pb.GetBackupRequest) (*
return nil, err
}
if len(backups) == 0 {
return nil, errors.New("No backup with such Id")
return nil, errors.New("No backup with such Id") // TODO: Permission denied?
}
// TODO: Need to check access to backup resource by backupID
if _, err := s.checkAuth(ctx, auth.PermissionBackupGet, backups[0].ContainerID, ""); err != nil {
return nil, err
}

xlog.Debug(ctx, "GetBackup", zap.String("backup", backups[0].String()))
return backups[0].Proto(), nil
}

func (s *server) MakeBackup(ctx context.Context, req *pb.MakeBackupRequest) (*pb.Operation, error) {
xlog.Info(ctx, "MakeBackup", zap.String("request", req.String()))
subject, err := s.checkAuth(ctx, auth.PermissionBackupCreate, req.ContainerId, "")
if err != nil {
return nil, err
}
xlog.Debug(ctx, "MakeBackup", zap.String("subject", subject))

clientConnectionParams := types.YdbConnectionParams{
Endpoint: req.GetDatabaseEndpoint(),
Expand Down Expand Up @@ -104,7 +170,7 @@ func (s *server) MakeBackup(ctx context.Context, req *pb.MakeBackupRequest) (*pb
return nil, fmt.Errorf("can't get S3SecretKey: %w", err)
}

dbNamePath := strings.Replace(req.DatabaseName, "/", "_", -1) // TODO: checking user imput
dbNamePath := strings.Replace(req.DatabaseName, "/", "_", -1) // TODO: checking user input
dbNamePath = strings.Trim(dbNamePath, "_")
dstPrefix := path.Join(s.s3.PathPrefix, dbNamePath)

Expand All @@ -130,7 +196,6 @@ func (s *server) MakeBackup(ctx context.Context, req *pb.MakeBackupRequest) (*pb
xlog.Debug(
ctx, "export operation started", zap.String("clientOperationID", clientOperationID), zap.String("dsn", dsn),
)
//TODO: forbid empty container id

backup := types.Backup{
ContainerID: req.GetContainerId(),
Expand Down Expand Up @@ -178,6 +243,12 @@ func (s *server) MakeBackup(ctx context.Context, req *pb.MakeBackupRequest) (*pb
func (s *server) MakeRestore(ctx context.Context, req *pb.MakeRestoreRequest) (*pb.Operation, error) {
xlog.Info(ctx, "MakeRestore", zap.String("request", req.String()))

subject, err := s.checkAuth(ctx, auth.PermissionBackupRestore, req.ContainerId, "") // TODO: check access to backup as resource
if err != nil {
return nil, err
}
xlog.Debug(ctx, "MakeRestore", zap.String("subject", subject))

clientConnectionParams := types.YdbConnectionParams{
Endpoint: req.GetDatabaseEndpoint(),
DatabaseName: req.GetDatabaseName(),
Expand Down Expand Up @@ -248,6 +319,10 @@ func (s *server) MakeRestore(ctx context.Context, req *pb.MakeRestoreRequest) (*

func (s *server) ListBackups(ctx context.Context, request *pb.ListBackupsRequest) (*pb.ListBackupsResponse, error) {
xlog.Debug(ctx, "ListBackups", zap.String("request", request.String()))
if _, err := s.checkAuth(ctx, auth.PermissionBackupList, request.ContainerId, ""); err != nil {
return nil, err
}

queryFilters := make([]queries.QueryFilter, 0)
//TODO: forbid empty containerId
if request.GetContainerId() != "" {
Expand All @@ -271,6 +346,7 @@ func (s *server) ListBackups(ctx context.Context, request *pb.ListBackupsRequest
},
)
}

backups, err := s.driver.SelectBackups(
ctx, queries.NewReadTableQuery(
queries.WithTableName("Backups"),
Expand All @@ -294,6 +370,10 @@ func (s *server) ListOperations(ctx context.Context, request *pb.ListOperationsR
*pb.ListOperationsResponse, error,
) {
xlog.Debug(ctx, "ListOperations", zap.String("request", request.String()))
if _, err := s.checkAuth(ctx, auth.PermissionBackupList, request.ContainerId, ""); err != nil {
return nil, err
}

queryFilters := make([]queries.QueryFilter, 0)
//TODO: forbid empty containerId
if request.GetContainerId() != "" {
Expand All @@ -317,6 +397,7 @@ func (s *server) ListOperations(ctx context.Context, request *pb.ListOperationsR
},
)
}

operations, err := s.driver.SelectOperations(
ctx, queries.NewReadTableQuery(
queries.WithTableName("Operations"),
Expand Down Expand Up @@ -401,11 +482,24 @@ func main() {
os.Exit(1)
}
clientConnector := client.NewClientYdbConnector(configInstance.ClientConnection)
var authProvider ap.AuthProvider
if len(configInstance.Auth.PluginPath) == 0 {
authProvider, err = auth.NewDummyAuthProvider(ctx)
} else {
authProvider, err = auth.NewAuthProvider(ctx, configInstance.Auth)

}
if err != nil {
xlog.Error(ctx, "Error init AuthProvider", zap.Error(err))
os.Exit(1)
}
defer authProvider.Finish(ctx)

server := server{
driver: dbConnector,
clientConn: clientConnector,
s3: configInstance.S3,
auth: authProvider,
}
defer server.driver.Close(ctx)

Expand Down
7 changes: 4 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ require (
github.com/ydb-platform/ydb-go-sdk/v3 v3.75.2
go.uber.org/automaxprocs v1.5.3
go.uber.org/zap v1.27.0
google.golang.org/grpc v1.64.1
google.golang.org/protobuf v1.34.1
google.golang.org/grpc v1.65.0
google.golang.org/protobuf v1.34.2
gopkg.in/yaml.v3 v3.0.1
)

Expand All @@ -28,5 +28,6 @@ require (
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/text v0.16.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 // indirect
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 // indirect
)
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@ google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfG
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 h1:Zy9XzmMEflZ/MAaA7vNcoebnRAld7FsPW1EeBB7V0m8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 h1:1GBuWVLM/KMVUv1t1En5Gs+gFZCNd360GGb4sSxtrhU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
Expand All @@ -155,6 +157,10 @@ google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG
google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA=
google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0=
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 h1:F29+wU6Ee6qgu9TddPgooOdaqsxTMunOoj8KA5yuS5A=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1/go.mod h1:5KF+wpkbTSbGcR9zteSqZV6fqFOWBl4Yde8En8MryZA=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
Expand All @@ -170,6 +176,8 @@ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
46 changes: 46 additions & 0 deletions internal/auth/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package auth

import (
"context"
"fmt"
"plugin"

"ydbcp/internal/config"
"ydbcp/internal/util/xlog"
"ydbcp/pkg/plugins/auth"

"go.uber.org/zap"
)

const (
PermissionBackupList = "ydb.databases.list"
PermissionBackupCreate = "ydb.databases.backup"
PermissionBackupRestore = "ydb.tables.create"
PermissionBackupGet = "ydb.databases.get"
)

func NewAuthProvider(ctx context.Context, cfg config.AuthConfig) (auth.AuthProvider, error) {
xlog.Info(ctx, "Loading auth provider plugin", zap.String("path", cfg.PluginPath))

plug, err := plugin.Open(cfg.PluginPath)
if err != nil {
return nil, fmt.Errorf("can't load auth provider plugin, path %s: %w", cfg.PluginPath, err)
}
symbol, err := plug.Lookup("AuthProvider")
if err != nil {
return nil, fmt.Errorf("can't lookup AuthProvider symbol, plugin path %s: %w", cfg.PluginPath, err)
}
var instance auth.AuthProvider
instance, ok := symbol.(auth.AuthProvider)
if !ok {
return nil, fmt.Errorf("can't cast AuthProvider symbol, plugin path %s", cfg.PluginPath)
}
pluginConfig, err := cfg.ConfigurationString()
if err != nil {
return nil, fmt.Errorf("can't get auth provider configuration: %w", err)
}
if err = instance.Init(ctx, pluginConfig); err != nil {
return nil, fmt.Errorf("can't initialize auth provider plugin: %w", err)
}
return instance, nil
}
Loading

0 comments on commit 5f40514

Please sign in to comment.