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 13, 2024
1 parent 6e0ce65 commit 4860090
Show file tree
Hide file tree
Showing 25 changed files with 2,830 additions and 29 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
14 changes: 12 additions & 2 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 ./...))
PACKAGES := $(filter-out ydbcp/pkg/proto/%, $(shell go list ./...))
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,10 @@ fmt:
lint:
golint $(PACKAGES)

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

plugins: auth_nebius
auth_nebius:
go build -C plugins/auth_nebius -buildmode=plugin -o auth_nebius.so
100 changes: 95 additions & 5 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,13 +48,34 @@ 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) GetBackup(ctx context.Context, request *pb.GetBackupRequest) (*pb.Backup, error) {
xlog.Debug(ctx, "GetBackup", zap.String("request", request.String()))
requestId, err := types.ParseObjectId(request.GetId())
if err != nil {
return nil, fmt.Errorf("failed to parse uuid %s: %w", request.GetId(), err)
return nil, fmt.Errorf("failed to parse uuid %s: %w", request.GetId(), err) // TODO: Permission denied?
}
backups, err := s.driver.SelectBackups(
ctx, queries.NewReadTableQuery(
Expand All @@ -64,18 +90,27 @@ func (s *server) GetBackup(ctx context.Context, request *pb.GetBackupRequest) (*
),
)
if err != nil {
xlog.Error(ctx, "can't select backups", zap.Error(err))
xlog.Error(ctx, "can't select backups", zap.Error(err)) // TODO: Permission denied?
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?
}
if _, err := s.checkAuth(ctx, "ydb.databases.get", 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, "ydb.databases.backup", 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 +139,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 Down Expand Up @@ -175,8 +210,44 @@ func (s *server) MakeBackup(ctx context.Context, req *pb.MakeBackupRequest) (*pb
return op.Proto(), 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 = ""
}
checks := []ap.AuthorizeCheck{
{
Permission: permission,
ContainerID: containerID,
},
}
if len(resourceID) > 0 {
checks[0].ResourceID = []string{resourceID}
}

resp, subject, err := s.auth.Authorize(ctx, token, checks)
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) ListBackups(ctx context.Context, request *pb.ListBackupsRequest) (*pb.ListBackupsResponse, error) {
xlog.Debug(ctx, "ListBackups", zap.String("request", request.String()))
if _, err := s.checkAuth(ctx, "ydb.databases.list", request.ContainerId, ""); err != nil {
return nil, err
}

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

backups, err := s.driver.SelectBackups(
ctx, queries.NewReadTableQuery(
queries.WithTableName("Backups"),
Expand All @@ -223,6 +295,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, "ydb.databases.list", request.ContainerId, ""); err != nil {
return nil, err
}

queryFilters := make([]queries.QueryFilter, 0)
//TODO: forbid empty containerId
if request.GetContainerId() != "" {
Expand All @@ -246,6 +322,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 @@ -330,11 +407,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
39 changes: 39 additions & 0 deletions internal/auth/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package auth

import (
"context"
"fmt"
"plugin"

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

"go.uber.org/zap"
)

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
}
78 changes: 78 additions & 0 deletions internal/auth/dummy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package auth

import (
"context"
"errors"
"fmt"
"ydbcp/internal/util/xlog"
"ydbcp/pkg/plugins/auth"

"go.uber.org/zap"
)

type authProviderDummy struct {
}

const (
anonymousSubject = "anonymous"
)

func (p *authProviderDummy) Init(ctx context.Context, config string) error {
xlog.Info(ctx, "AuthProviderDummy init", zap.String("config", config))
return nil
}

func (p *authProviderDummy) Finish(ctx context.Context) error {
xlog.Info(ctx, "AuthProviderDummy finish")
return nil
}

func (p *authProviderDummy) Authenticate(ctx context.Context, token string) (string, auth.AuthCode, error) {
xlog.Debug(
ctx,
"AuthProviderDummy Authenticate",
zap.String("token", auth.MaskToken(token)),
)
code := auth.AuthCodeSuccess
xlog.Debug(ctx, "AuthProviderDummy authenticate result",
zap.String("token", auth.MaskToken(token)),
zap.String("code", code.String()),
zap.String("subject", anonymousSubject),
)
return anonymousSubject, code, nil
}

func (p *authProviderDummy) Authorize(
ctx context.Context,
token string,
checks []auth.AuthorizeCheck,
) (results []auth.AuthorizeResult, subject string, err error) {
xlog.Info(
ctx,
"AuthProviderDummy Authorize",
zap.String("token", auth.MaskToken(token)),
zap.String("checks", fmt.Sprintf("%v", checks)),
)
if len(checks) == 0 {
xlog.Error(ctx, "AuthProviderDummy AuthorizeCheck list is empty")
return nil, "", errors.New("AuthorizeCheck list is empty")
}

results = make([]auth.AuthorizeResult, 0, len(checks))
for range len(checks) {
results = append(results, auth.AuthorizeResult{Code: auth.AuthCodeSuccess})
}
xlog.Info(ctx, "AuthProviderDummy Authorize result",
zap.String("results", fmt.Sprintf("%v", results)),
zap.String("subject", anonymousSubject),
)
return results, subject, nil
}

func NewDummyAuthProvider(ctx context.Context) (auth.AuthProvider, error) {
p := &authProviderDummy{}
if err := p.Init(ctx, ""); err != nil {
return nil, err
}
return p, nil
}
Loading

0 comments on commit 4860090

Please sign in to comment.