diff --git a/app/config/config.go b/app/config/config.go index 29bde99..8d4b46f 100644 --- a/app/config/config.go +++ b/app/config/config.go @@ -46,6 +46,7 @@ type Config struct { Keypath string `json:"keypath,omitempty" yaml:"keypath"` X509Cert string `json:"-" yaml:"-"` X509Key string `json:"-" yaml:"-"` + Datadir string `json:"datadir"` } func NewConfig() *Config { @@ -76,7 +77,7 @@ func NewConfig() *Config { Logpath: logpath, Runpath: runpath, Version: version, - + Datadir: datadir, //Certpath: certpath, //Keypath: keypath, } diff --git a/app/descr/grant.go b/app/descr/grant.go index 9425be6..1bf2bd0 100644 --- a/app/descr/grant.go +++ b/app/descr/grant.go @@ -10,12 +10,20 @@ package descr -const AnonymousID = "10000000-0000-0000-0000-000000000001" +const ( + AnonimousUsername = "anonymous" + AnonymousID = "10000000-0000-0000-0000-000000000001" + ServerUsername = "server" + ServerID = "10000000-0000-0000-0000-000000000002" + + InitUsername = "mstore" + InitID = "10000000-0000-0000-0000-000000000005" +) type Grant struct { ID string `json:"id" db:"id"` AccountID string `json:"accountID" db:"account_id"` - Operation string `json:"operation" db:"operation"` + Right string `json:"right" db:"right"` Pattern string `json:"pattern" db:"pattern"` CreatedAt string `json:"createdAt" db:"created_at"` UpdatedAt string `json:"updatedAt" db:"updated_at"` @@ -24,7 +32,7 @@ type Grant struct { } type GrantShort struct { - Operation string `json:"operation" db:"operation"` + Right string `json:"right" db:"right"` Pattern string `json:"pattern" db:"pattern"` CreatedAt string `json:"createdAt" db:"created_at"` UpdatedAt string `json:"updatedAt" db:"updated_at"` @@ -33,5 +41,16 @@ type GrantShort struct { } const ( - GrantCreateAccount = "createAccount" + // Accounts + RightReadAccounts = "readAccounts" // GetAccount, ListAccounts + RightWriteAccounts = "writeAccounts" // CreateAccount, UpdateAccount, DeleteAccount + // Frants + RightReadGrants = "readGrants" // Like account operation + RightWriteGrants = "writeGrants" + // Files + RightWriteFiles = "writeFiles" // FileInfo, GetFile, ListFiles + RightReadFiles = "readFiles" // PutFile, DeleteFile + // Images: manifests, layers + RightReadImages = "readImages" // ManifestInfo, GetManifest, BlobInfo, GetBlob + RightWriteImages = "writeImages" // other opearion ) diff --git a/app/descr/server.go b/app/descr/server.go new file mode 100644 index 0000000..64af171 --- /dev/null +++ b/app/descr/server.go @@ -0,0 +1,17 @@ +/* + * Copyright 2026 Oleg Borodin + * + * This work is published and licensed under a Creative Commons + * Attribution-NonCommercial-NoDerivatives 4.0 International License. + * + * Distribution of this work is permitted, but commercial use and + * modifications are strictly prohibited. + */ +package descr + +type Server struct { + SchemeCreated bool `json:"SchemeCreated"` + AnonymousCreated bool `json:"AnonymousCreated"` + InituserCreated bool `json:"inituserCreated"` + SchemeCreatedAt string `json:"inituserCreatedAt"` +} diff --git a/app/handler/account.go b/app/handler/account.go index cb5c8d6..741fd2c 100644 --- a/app/handler/account.go +++ b/app/handler/account.go @@ -9,10 +9,12 @@ import ( "mstore/app/router" ) -func (hand *Handler) CheckGrant(ctx context.Context, accountID, grant, subject string) (bool, error) { +func (hand *Handler) CheckRight(ctx context.Context, accountID, right, subject string) (bool, error) { var err error var res bool + hand.logg.Debugf("CheckRight %s: %s %s", accountID, right, subject) + res = true return res, err } @@ -22,9 +24,13 @@ func (hand *Handler) CreateAccount(rctx *router.Context) { params := &operator.CreateAccountParams{} err = rctx.BindJSON(params) + if err != nil { + hand.SendError(rctx, err) + return + } operatorID, _ := rctx.GetString(userTag) - opEnable, err := hand.CheckGrant(rctx.Ctx, operatorID, descr.GrantCreateAccount, params.Username) + opEnable, err := hand.CheckRight(rctx.Ctx, operatorID, descr.RightWriteAccounts, params.Username) if err != nil { err := fmt.Errorf("CreateAccount error: %v", err) hand.SendError(rctx, err) @@ -36,10 +42,6 @@ func (hand *Handler) CreateAccount(rctx *router.Context) { return } - if err != nil { - hand.SendError(rctx, err) - return - } res, err := hand.oper.CreateAccount(rctx.Ctx, operatorID, params) if err != nil { hand.logg.Errorf("CreateAccount error: %v", err) diff --git a/app/handler/grant.go b/app/handler/grant.go index 0cefefb..68389d7 100644 --- a/app/handler/grant.go +++ b/app/handler/grant.go @@ -1,3 +1,12 @@ +/* + * Copyright 2026 Oleg Borodin + * + * This work is published and licensed under a Creative Commons + * Attribution-NonCommercial-NoDerivatives 4.0 International License. + * + * Distribution of this work is permitted, but commercial use and + * modifications are strictly prohibited. + */ package handler import ( diff --git a/app/maindb/grant.go b/app/maindb/grant.go index 481cf56..44d24fe 100644 --- a/app/maindb/grant.go +++ b/app/maindb/grant.go @@ -1,3 +1,12 @@ +/* + * Copyright 2026 Oleg Borodin + * + * This work is published and licensed under a Creative Commons + * Attribution-NonCommercial-NoDerivatives 4.0 International License. + * + * Distribution of this work is permitted, but commercial use and + * modifications are strictly prohibited. + */ package maindb import ( @@ -8,9 +17,9 @@ import ( func (db *Database) InsertGrant(ctx context.Context, grant *descr.Grant) error { var err error - request := `INSERT INTO grants(id, account_id, operation, pattern, created_at, updated_at, created_by, updated_by) + request := `INSERT INTO grants(id, account_id, right, pattern, created_at, updated_at, created_by, updated_by) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)` - _, err = db.db.Exec(request, grant.ID, grant.AccountID, grant.Operation, grant.Pattern, + _, err = db.db.Exec(request, grant.ID, grant.AccountID, grant.Right, grant.Pattern, grant.CreatedAt, grant.UpdatedAt, grant.CreatedBy, grant.UpdatedBy) if err != nil { return err @@ -67,12 +76,12 @@ func (db *Database) GetGrantByID(ctx context.Context, id string) (bool, *descr.G return true, res, err } -func (db *Database) GetGrantByAccoundIDOperationPattern(ctx context.Context, accountID, operation, pattern string) (bool, *descr.Grant, error) { +func (db *Database) GetGrantByAccoundIDRightPattern(ctx context.Context, accountID, right, pattern string) (bool, *descr.Grant, error) { var err error res := &descr.Grant{} - request := `SELECT * FROM grants WHERE account_id = $1 AND operation = $2 AND pattern = $3 LIMIT 1` + request := `SELECT * FROM grants WHERE account_id = $1 AND right = $2 AND pattern = $3 LIMIT 1` dbRes := make([]descr.Grant, 0) - err = db.db.Select(&dbRes, request, accountID, operation, pattern) + err = db.db.Select(&dbRes, request, accountID, right, pattern) if err != nil { return false, res, err } @@ -84,10 +93,10 @@ func (db *Database) GetGrantByAccoundIDOperationPattern(ctx context.Context, acc return true, res, err } -func (db *Database) DeleteGrantByAccountIDOperationPattern(ctx context.Context, accountID, operation, pattern string) error { +func (db *Database) DeleteGrantByAccountIDRightPattern(ctx context.Context, accountID, right, pattern string) error { var err error - request := `DELETE FROM grants WHERE account_id = $1 AND operation = $2 AND pattern = $3` - _, err = db.db.Exec(request, accountID, operation, pattern) + request := `DELETE FROM grants WHERE account_id = $1 AND right = $2 AND pattern = $3` + _, err = db.db.Exec(request, accountID, right, pattern) if err != nil { return err } diff --git a/app/maindb/init.go b/app/maindb/init.go new file mode 100644 index 0000000..0fcb3b3 --- /dev/null +++ b/app/maindb/init.go @@ -0,0 +1,154 @@ +/* + * Copyright 2026 Oleg Borodin + * + * This work is published and licensed under a Creative Commons + * Attribution-NonCommercial-NoDerivatives 4.0 International License. + * + * Distribution of this work is permitted, but commercial use and + * modifications are strictly prohibited. + */ + +package maindb + +import ( + "context" + + "mstore/app/descr" + "mstore/pkg/auxpwd" + "mstore/pkg/auxtool" + "mstore/pkg/auxuuid" +) + +func (db *Database) WriteAnonymous(ctx context.Context) error { + var err error + + now := auxtool.TimeNow() + password := auxtool.RandomString(64) + passhash := auxpwd.MakeSHA256Hash([]byte(password)) + accountDescr := &descr.Account{ + ID: auxuuid.NewUUID(), + Username: descr.AnonimousUsername, + Passhash: passhash, + Disabled: false, + CreatedAt: now, + UpdatedAt: now, + CreatedBy: descr.ServerID, + UpdatedBy: descr.ServerID, + } + err = db.InsertAccount(ctx, accountDescr) + if err != nil { + return err + } + grantDescr := &descr.Grant{ + ID: auxuuid.NewUUID(), + AccountID: accountDescr.ID, + Right: descr.RightReadFiles, + Pattern: "*", + CreatedAt: now, + UpdatedAt: now, + CreatedBy: descr.ServerID, + UpdatedBy: descr.ServerID, + } + err = db.InsertGrant(ctx, grantDescr) + if err != nil { + return err + } + grantDescr = &descr.Grant{ + ID: auxuuid.NewUUID(), + AccountID: accountDescr.ID, + Right: descr.RightReadFiles, + Pattern: "*", + CreatedAt: now, + UpdatedAt: now, + CreatedBy: descr.ServerID, + UpdatedBy: descr.ServerID, + } + err = db.InsertGrant(ctx, grantDescr) + if err != nil { + return err + } + + return err + +} + +func (db *Database) WriteInituser(ctx context.Context) error { + var err error + + now := auxtool.TimeNow() + passhash := auxpwd.MakeSHA256Hash([]byte(descr.InitUsername)) + accountDescr := &descr.Account{ + ID: auxuuid.NewUUID(), + Username: descr.AnonimousUsername, + Passhash: passhash, + Disabled: false, + CreatedAt: now, + UpdatedAt: now, + CreatedBy: descr.ServerID, + UpdatedBy: descr.ServerID, + } + err = db.InsertAccount(ctx, accountDescr) + if err != nil { + return err + } + // Files + grantDescr := &descr.Grant{ + ID: auxuuid.NewUUID(), + AccountID: accountDescr.ID, + Right: descr.RightReadFiles, + Pattern: "*", + CreatedAt: now, + UpdatedAt: now, + CreatedBy: descr.ServerID, + UpdatedBy: descr.ServerID, + } + err = db.InsertGrant(ctx, grantDescr) + if err != nil { + return err + } + grantDescr = &descr.Grant{ + ID: auxuuid.NewUUID(), + AccountID: accountDescr.ID, + Right: descr.RightWriteFiles, + Pattern: "*", + CreatedAt: now, + UpdatedAt: now, + CreatedBy: descr.ServerID, + UpdatedBy: descr.ServerID, + } + err = db.InsertGrant(ctx, grantDescr) + if err != nil { + return err + } + // Images + grantDescr = &descr.Grant{ + ID: auxuuid.NewUUID(), + AccountID: accountDescr.ID, + Right: descr.RightReadImages, + Pattern: "*", + CreatedAt: now, + UpdatedAt: now, + CreatedBy: descr.ServerID, + UpdatedBy: descr.ServerID, + } + err = db.InsertGrant(ctx, grantDescr) + if err != nil { + return err + } + grantDescr = &descr.Grant{ + ID: auxuuid.NewUUID(), + AccountID: accountDescr.ID, + Right: descr.RightWriteImages, + Pattern: "*", + CreatedAt: now, + UpdatedAt: now, + CreatedBy: descr.ServerID, + UpdatedBy: descr.ServerID, + } + err = db.InsertGrant(ctx, grantDescr) + if err != nil { + return err + } + return err + +} diff --git a/app/operator/account.go b/app/operator/account.go index 93c9054..04f91e3 100644 --- a/app/operator/account.go +++ b/app/operator/account.go @@ -110,8 +110,12 @@ func (oper *Operator) GetAccount(ctx context.Context, params *GetAccountParams) } for _, grantDescrs := range grantDescrs { grantShorts := descr.GrantShort{ - Operation: grantDescrs.Operation, + Right: grantDescrs.Right, + Pattern: grantDescrs.Pattern, CreatedAt: grantDescrs.CreatedAt, + UpdatedAt: grantDescrs.UpdatedAt, + CreatedBy: grantDescrs.CreatedBy, + UpdatedBy: grantDescrs.UpdatedBy, } accountShort.Grants = append(accountShort.Grants, grantShorts) } @@ -253,8 +257,12 @@ func (oper *Operator) ListAccounts(ctx context.Context, params *ListAccountsPara } for _, grantDescrs := range grantDescrs { grantShorts := descr.GrantShort{ - Operation: grantDescrs.Operation, + Right: grantDescrs.Right, + Pattern: grantDescrs.Pattern, CreatedAt: grantDescrs.CreatedAt, + UpdatedAt: grantDescrs.UpdatedAt, + CreatedBy: grantDescrs.CreatedBy, + UpdatedBy: grantDescrs.UpdatedBy, } accountShort.Grants = append(accountShort.Grants, grantShorts) } diff --git a/app/operator/grant.go b/app/operator/grant.go index d54ae9f..a419049 100644 --- a/app/operator/grant.go +++ b/app/operator/grant.go @@ -11,7 +11,7 @@ import ( type CreateGrantParams struct { AccountID string `json:"accountID"` - Operation string `json:"operation"` + Right string `json:"operation"` Pattern string `json:"pattern"` } type CreateGrantResult struct { @@ -26,7 +26,7 @@ func (oper *Operator) CreateGrant(ctx context.Context, params *CreateGrantParams err := fmt.Errorf("Empty accountId parameters") return res, err } - if params.Operation == "" { + if params.Right == "" { err := fmt.Errorf("Empty operation parameter") return res, err } @@ -35,7 +35,7 @@ func (oper *Operator) CreateGrant(ctx context.Context, params *CreateGrantParams return res, err } - grantExists, _, err := oper.mdb.GetGrantByAccoundIDOperationPattern(ctx, params.AccountID, params.Operation, params.Pattern) + grantExists, _, err := oper.mdb.GetGrantByAccoundIDRightPattern(ctx, params.AccountID, params.Right, params.Pattern) if err != nil { return res, err } @@ -47,7 +47,7 @@ func (oper *Operator) CreateGrant(ctx context.Context, params *CreateGrantParams grantDescr := &descr.Grant{ ID: auxuuid.NewUUID(), AccountID: params.AccountID, - Operation: params.Operation, + Right: params.Right, Pattern: params.Pattern, CreatedAt: now, UpdatedAt: now, diff --git a/app/server/server.go b/app/server/server.go index d0055fe..5174c9a 100644 --- a/app/server/server.go +++ b/app/server/server.go @@ -11,20 +11,26 @@ package server import ( + "context" + "io/ioutil" "os" "os/signal" "os/user" "path/filepath" "strconv" "syscall" + "time" "mstore/app/config" + "mstore/app/descr" "mstore/app/handler" "mstore/app/logger" "mstore/app/maindb" "mstore/app/operator" "mstore/app/service" "mstore/app/storage" + + "sigs.k8s.io/yaml" ) type Server struct { @@ -35,6 +41,7 @@ type Server struct { hand *handler.Handler logg *logger.Logger stor *storage.Storage + stat descr.Server } func NewServer() (*Server, error) { @@ -95,50 +102,102 @@ func (srv *Server) Configure() error { func (srv *Server) Build() error { var err error - srv.logg.Infof("Server build") + srv.logg.Infof("Server building") confDump := srv.conf.String() srv.logg.Infof("Current server configuration is:\n%s\n", confDump) if srv.conf.AsDaemon { logdir := filepath.Dir(srv.conf.Logpath) - srv.logg.Infof("Create log directory %s", logdir) + srv.logg.Infof("Creating log directory %s", logdir) err = os.MkdirAll(logdir, 0750) if err != nil { return err } rundir := filepath.Dir(srv.conf.Runpath) - srv.logg.Infof("Create run directory %s", rundir) + srv.logg.Infof("Creating run directory %s", rundir) err = os.MkdirAll(rundir, 0750) if err != nil { return err } } + // Creating datadir + datadir := srv.conf.Datadir + srv.logg.Infof("Creating data directory %s ", datadir) + err = os.MkdirAll(datadir, 0750) + if err != nil { + return err + } + + // Read state file + statefilePath := filepath.Join(srv.conf.Datadir, "server.yaml") + stateData, err := ioutil.ReadFile(statefilePath) + if err != nil { + err = yaml.Unmarshal(stateData, &srv.stat) + if err != nil { + return err + } + } + // Creating database dbdir := srv.conf.Database.Basepath - srv.logg.Infof("Create database directory %s ", dbdir) + srv.logg.Infof("Creating database directory %s ", dbdir) err = os.MkdirAll(dbdir, 0750) if err != nil { return err } mdb := maindb.NewDatabase(dbdir) - srv.logg.Infof("Open main database") + srv.logg.Infof("Opening main database") err = mdb.OpenDatabase() if err != nil { return err } - srv.logg.Infof("Initialize main database") - err = mdb.InitDatabase() - if err != nil { - return err - } srv.mdb = mdb + ctx, _ := context.WithTimeout(context.Background(), 5*time.Second) + // Created db scheme + if !srv.stat.SchemeCreated { + srv.logg.Infof("Initialize main database") + err = mdb.InitDatabase() + if err != nil { + return err + } + srv.stat.SchemeCreated = true + } + // Created anonymous user + if !srv.stat.AnonymousCreated { + srv.logg.Infof("Creating anonimous user") + err = mdb.WriteAnonymous(ctx) + if err != nil { + return err + } + srv.stat.AnonymousCreated = true + } + if !srv.stat.InituserCreated { + srv.logg.Infof("Creating init user") + err = srv.mdb.WriteAnonymous(ctx) + if err != nil { + return err + } + srv.stat.InituserCreated = true + } + + // Write status file + stateData, err = yaml.Marshal(srv.stat) + if err != nil { + return err + } + + err = ioutil.WriteFile(statefilePath, stateData, 0640) + if err != nil { + return err + } + // Creating storage - srv.logg.Infof("Create storage directory") - datadir := srv.conf.Database.Basepath + srv.logg.Infof("Creating storage directory") + datadir = srv.conf.Database.Basepath err = os.MkdirAll(datadir, 0750) if err != nil { return err diff --git a/pkg/auxtool/fileex.go b/pkg/auxtool/fileex.go new file mode 100644 index 0000000..4123c78 --- /dev/null +++ b/pkg/auxtool/fileex.go @@ -0,0 +1,28 @@ +package auxtool + +import ( + "os" +) + +func FileExists(name string) bool { + fileStat, err := os.Stat(name) + if err != nil { + if os.IsNotExist(err) { + return false + } + } + if fileStat.IsDir() { + return false + } + return true +} + +func DirExists(name string) bool { + fileStat, err := os.Stat(name) + if err != nil { + if os.IsNotExist(err) { + return false + } + } + return fileStat.IsDir() +}