mirror of
https://github.com/project-zot/zot.git
synced 2026-06-15 11:37:56 +08:00
2402296e9a
* fix: migrate to Go module v2 for proper semantic versioning This change updates the module path from 'zotregistry.dev/zot' to 'zotregistry.dev/zot/v2' to comply with Go's semantic versioning rules. According to Go's module versioning requirements, major version v2+ must include the major version in the module path. The current module path 'zotregistry.dev/zot' only supports v0.x.x and v1.x.x versions, making existing v2.x.x tags (like v2.1.8) unusable. Changes: - Updated go.mod module path to zotregistry.dev/zot/v2 - Updated all internal import paths across 280+ Go source files - Updated configuration files (golangcilint.yaml, gqlgen.yml) - Updated README.md Go reference badge This fix enables proper use of existing v2.x.x Git tags and allows external packages to import zot v2+ versions without compatibility errors. Resolves: Go module import compatibility for v2+ versions Fixes: #3071 Signed-off-by: Luca Muscariello <muscariello@ieee.org> * fix: regenerate GraphQL files with updated v2 import paths The gqlgen tool needs to regenerate the GraphQL schema files after the module path change to use the new v2 imports. Signed-off-by: Luca Muscariello <muscariello@ieee.org> --------- Signed-off-by: Luca Muscariello <muscariello@ieee.org>
280 lines
6.3 KiB
Go
280 lines
6.3 KiB
Go
package api
|
|
|
|
import (
|
|
"context"
|
|
"encoding/gob"
|
|
"fmt"
|
|
"io/fs"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gorilla/sessions"
|
|
"github.com/rbcervilla/redisstore/v9"
|
|
|
|
"zotregistry.dev/zot/v2/errors"
|
|
"zotregistry.dev/zot/v2/pkg/api/config"
|
|
rediscfg "zotregistry.dev/zot/v2/pkg/api/config/redis"
|
|
"zotregistry.dev/zot/v2/pkg/log"
|
|
"zotregistry.dev/zot/v2/pkg/scheduler"
|
|
"zotregistry.dev/zot/v2/pkg/storage"
|
|
storageConstants "zotregistry.dev/zot/v2/pkg/storage/constants"
|
|
)
|
|
|
|
const cookiesMaxAge = 7200 // 2h
|
|
|
|
type CookieStore struct {
|
|
needsCleanup bool // if store should be periodically cleaned
|
|
rootDir string
|
|
sessions.Store
|
|
}
|
|
|
|
func (c *CookieStore) RunSessionCleaner(sch *scheduler.Scheduler) {
|
|
if c.needsCleanup {
|
|
sch.SubmitGenerator(
|
|
&SessionCleanup{rootDir: c.rootDir},
|
|
cookiesMaxAge*time.Second,
|
|
scheduler.LowPriority,
|
|
)
|
|
}
|
|
}
|
|
|
|
func NewCookieStore(
|
|
authCfg *config.AuthConfig,
|
|
storeController storage.StoreController,
|
|
log log.Logger,
|
|
) (*CookieStore, error) {
|
|
// To store custom types in our cookies
|
|
// we must first register them using gob.Register
|
|
gob.Register(map[string]interface{}{})
|
|
|
|
var store sessions.Store
|
|
|
|
var sessionsDir string
|
|
|
|
var needsCleanup bool
|
|
|
|
if authCfg.SessionDriver == nil {
|
|
// If the session driver is not configured, then
|
|
// behave in the usual way for file system cookie store and memory cookie store.
|
|
createdStore, returnedSessionsDir, doesStoreNeedCleanup, err := localSessionStoreInit(
|
|
storeController,
|
|
authCfg.SessionHashKey,
|
|
authCfg.SessionEncryptKey,
|
|
)
|
|
if err != nil {
|
|
return &CookieStore{}, err
|
|
}
|
|
|
|
store = createdStore
|
|
sessionsDir = returnedSessionsDir
|
|
needsCleanup = doesStoreNeedCleanup
|
|
} else {
|
|
switch authCfg.SessionDriver["name"] {
|
|
case storageConstants.RedisDriverName:
|
|
{
|
|
prefix, ok := rediscfg.GetString(authCfg.SessionDriver, "keyprefix", false, log)
|
|
if !ok {
|
|
prefix = "zotsession"
|
|
}
|
|
|
|
// The redisstore library code uses a colon to separate the prefix
|
|
// and the actual key and is expected to be part of the prefix argument.
|
|
// ref: https://github.com/rbcervilla/redisstore/blob/v9.0.0/redisstore.go#L44
|
|
// This adds a colon to the prefix only if it is not empty.
|
|
if prefix != "" {
|
|
prefix += ":"
|
|
}
|
|
|
|
client, err := rediscfg.GetRedisClient(authCfg.SessionDriver, log)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
redisStore, err := redisstore.NewRedisStore(context.Background(), client)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
redisStore.KeyPrefix(prefix)
|
|
redisStore.Options(sessions.Options{
|
|
MaxAge: cookiesMaxAge,
|
|
Path: "/",
|
|
})
|
|
|
|
store = redisStore
|
|
}
|
|
case storageConstants.LocalStorageDriverName:
|
|
{
|
|
// This behaves the same as if there was no sessionDriver config.
|
|
// It is also the same behaviour prior to supporting this config.
|
|
// This allows for a backwards compatible migration path for upgrades.
|
|
createdStore, sessDir, cleanupReq, err := localSessionStoreInit(
|
|
storeController,
|
|
authCfg.SessionHashKey,
|
|
authCfg.SessionEncryptKey,
|
|
)
|
|
if err != nil {
|
|
return &CookieStore{}, err
|
|
}
|
|
|
|
store = createdStore
|
|
sessionsDir = sessDir
|
|
needsCleanup = cleanupReq
|
|
}
|
|
default:
|
|
return nil, fmt.Errorf(
|
|
"%w: sessiondriver %s not supported",
|
|
errors.ErrBadConfig,
|
|
authCfg.SessionDriver["name"],
|
|
)
|
|
}
|
|
}
|
|
|
|
return &CookieStore{
|
|
Store: store,
|
|
rootDir: sessionsDir,
|
|
needsCleanup: needsCleanup,
|
|
}, nil
|
|
}
|
|
|
|
// Handles creation and init of a local session store.
|
|
// This can be either in memory or on the local file system.
|
|
// Returns a session Store, root directory for the sessions if applicable,
|
|
// a boolean indicating whether clean up is required, and an error.
|
|
func localSessionStoreInit(
|
|
storeController storage.StoreController,
|
|
hashKey,
|
|
encryptKey []byte,
|
|
) (sessions.Store, string, bool, error) {
|
|
var store sessions.Store
|
|
|
|
var sessionsDir string
|
|
|
|
var needsCleanup bool
|
|
|
|
if storeController.DefaultStore.Name() == storageConstants.LocalStorageDriverName {
|
|
sessionsDir = path.Join(storeController.DefaultStore.RootDir(), "_sessions")
|
|
if err := os.MkdirAll(sessionsDir, storageConstants.DefaultDirPerms); err != nil {
|
|
return &CookieStore{}, "", false, err
|
|
}
|
|
|
|
localStore := sessions.NewFilesystemStore(sessionsDir, hashKey, encryptKey)
|
|
|
|
localStore.MaxAge(cookiesMaxAge)
|
|
|
|
store = localStore
|
|
needsCleanup = true
|
|
} else {
|
|
memStore := sessions.NewCookieStore(hashKey, encryptKey)
|
|
|
|
memStore.MaxAge(cookiesMaxAge)
|
|
|
|
store = memStore
|
|
}
|
|
|
|
return store, sessionsDir, needsCleanup, nil
|
|
}
|
|
|
|
func IsExpiredSession(dirEntry fs.DirEntry) bool {
|
|
fileInfo, err := dirEntry.Info()
|
|
if err != nil { // may have been deleted in the meantime
|
|
return false
|
|
}
|
|
|
|
if !strings.HasPrefix(fileInfo.Name(), "session_") {
|
|
return false
|
|
}
|
|
|
|
if fileInfo.ModTime().Add(cookiesMaxAge * time.Second).After(time.Now()) {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func getExpiredSessions(dir string) ([]string, error) {
|
|
sessions := make([]string, 0)
|
|
|
|
err := filepath.WalkDir(dir, func(filePath string, dirEntry fs.DirEntry, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if IsExpiredSession(dirEntry) {
|
|
sessions = append(sessions, filePath)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
if os.IsNotExist(err) {
|
|
return sessions, nil
|
|
}
|
|
|
|
return sessions, err
|
|
}
|
|
|
|
type SessionCleanup struct {
|
|
rootDir string
|
|
done bool
|
|
}
|
|
|
|
func (gen *SessionCleanup) Name() string {
|
|
return "SessionCleanupGenerator"
|
|
}
|
|
|
|
func (gen *SessionCleanup) Next() (scheduler.Task, error) {
|
|
sessions, err := getExpiredSessions(gen.rootDir)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(sessions) == 0 {
|
|
gen.done = true
|
|
|
|
return nil, nil //nolint:nilnil
|
|
}
|
|
|
|
return &CleanTask{sessions: sessions}, nil
|
|
}
|
|
|
|
func (gen *SessionCleanup) IsDone() bool {
|
|
return gen.done
|
|
}
|
|
|
|
func (gen *SessionCleanup) IsReady() bool {
|
|
return true
|
|
}
|
|
|
|
func (gen *SessionCleanup) Reset() {
|
|
gen.done = false
|
|
}
|
|
|
|
type CleanTask struct {
|
|
sessions []string
|
|
}
|
|
|
|
func (cleanTask *CleanTask) DoWork(ctx context.Context) error {
|
|
for _, session := range cleanTask.sessions {
|
|
if err := os.Remove(session); err != nil {
|
|
if !os.IsNotExist(err) {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (cleanTask *CleanTask) String() string {
|
|
return fmt.Sprintf("{Name: %s, sessions: %s}",
|
|
cleanTask.Name(), cleanTask.sessions)
|
|
}
|
|
|
|
func (cleanTask *CleanTask) Name() string {
|
|
return "SessionCleanupTask"
|
|
}
|