0
Fork 0
mirror of https://github.com/project-zot/zot.git synced 2024-12-16 21:56:37 -05:00

fix(authn): make hashing/encryption keys used to secure cookies (#2536)

fix(authn): configurable hashing/encryption keys used to secure cookies

If they are not configured zot will generate a random hashing key at startup,
invalidating all cookies if zot is restarted. closes: #2526

Signed-off-by: Petu Eusebiu <peusebiu@cisco.com>
This commit is contained in:
peusebiu 2024-08-13 01:11:53 +03:00 committed by GitHub
parent 17dbb56ea1
commit b461619682
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 219 additions and 71 deletions

View file

@ -370,6 +370,36 @@ Using that cookie on subsequent calls will authenticate them, asumming the cooki
In case of using filesystem storage sessions are saved in zot's root directory. In case of using filesystem storage sessions are saved in zot's root directory.
In case of using cloud storage sessions are saved in memory. In case of using cloud storage sessions are saved in memory.
### Securing session based login
In order to secure session cookies used in session based authentication process you need to set the path to a file containg keys used to hash and encrypt the cookies:
`sessionKeysFile`
```
"auth": {
"htpasswd": {
"path": "test/data/htpasswd"
},
"sessionKeysFile": "/home/user/keys",
"apikey": true,
}
```
```
user@host:~/zot$ cat ../keys | jq
{
"hashKey": "my-very-secret",
"encryptKey": "another-secret"
}
```
- hashKey - used to authenticate the cookie value using HMAC. It is recommended to use a key with exactly 32 or 64 bytes.
- encryptKey - this is optional, used to encrypt the cookie value. If set, the length must correspond to the block size of the encryption algorithm. For AES, used by default, valid lengths are 16, 24, or 32 bytes to select AES-128, AES-192, or AES-256.
If at least hashKey is not set zot will create a random one which on zot restarts it will invalidate all currently valid cookies and their sessions, requiring all users to login again.
#### API keys #### API keys
zot allows authentication for REST API calls using your API key as an alternative to your password. zot allows authentication for REST API calls using your API key as an alternative to your password.

View file

@ -13,6 +13,7 @@
"htpasswd": { "htpasswd": {
"path": "test/data/htpasswd" "path": "test/data/htpasswd"
}, },
"sessionKeysFile": "examples/sessionKeys.json",
"apikey": true, "apikey": true,
"openid": { "openid": {
"providers": { "providers": {

View file

@ -0,0 +1,4 @@
{
"hashKey": "3lrioGLGO2RfG9Y7HQGgWa3ayBjMLw2auMXqEWcSXjQKc9SoQ3fKTIbO+toPYa7e",
"encryptKey": "KOzt01JrDz2uC//UBC5ZikxQw4owfmI8"
}

View file

@ -22,7 +22,6 @@ import (
"github.com/google/go-github/v52/github" "github.com/google/go-github/v52/github"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/gorilla/securecookie"
"github.com/gorilla/sessions" "github.com/gorilla/sessions"
godigest "github.com/opencontainers/go-digest" godigest "github.com/opencontainers/go-digest"
"github.com/zitadel/oidc/v3/pkg/client/rp" "github.com/zitadel/oidc/v3/pkg/client/rp"
@ -334,10 +333,12 @@ func (amw *AuthnMiddleware) tryAuthnHandlers(ctlr *Controller) mux.MiddlewareFun
for provider := range ctlr.Config.HTTP.Auth.OpenID.Providers { for provider := range ctlr.Config.HTTP.Auth.OpenID.Providers {
if config.IsOpenIDSupported(provider) { if config.IsOpenIDSupported(provider) {
rp := NewRelyingPartyOIDC(context.TODO(), ctlr.Config, provider, ctlr.Log) rp := NewRelyingPartyOIDC(context.TODO(), ctlr.Config, provider, ctlr.Config.HTTP.Auth.SessionHashKey,
ctlr.Config.HTTP.Auth.SessionEncryptKey, ctlr.Log)
ctlr.RelyingParties[provider] = rp ctlr.RelyingParties[provider] = rp
} else if config.IsOauth2Supported(provider) { } else if config.IsOauth2Supported(provider) {
rp := NewRelyingPartyGithub(ctlr.Config, provider, ctlr.Log) rp := NewRelyingPartyGithub(ctlr.Config, provider, ctlr.Config.HTTP.Auth.SessionHashKey,
ctlr.Config.HTTP.Auth.SessionEncryptKey, ctlr.Log)
ctlr.RelyingParties[provider] = rp ctlr.RelyingParties[provider] = rp
} }
} }
@ -610,20 +611,25 @@ func (rh *RouteHandler) AuthURLHandler() http.HandlerFunc {
} }
} }
func NewRelyingPartyOIDC(ctx context.Context, config *config.Config, provider string, log log.Logger) rp.RelyingParty { func NewRelyingPartyOIDC(ctx context.Context, config *config.Config, provider string,
issuer, clientID, clientSecret, redirectURI, scopes, options := getRelyingPartyArgs(config, provider, log) hashKey, encryptKey []byte, log log.Logger,
) rp.RelyingParty {
issuer, clientID, clientSecret, redirectURI, scopes, options := getRelyingPartyArgs(config,
provider, hashKey, encryptKey, log)
relyingParty, err := rp.NewRelyingPartyOIDC(ctx, issuer, clientID, clientSecret, redirectURI, scopes, options...) relyingParty, err := rp.NewRelyingPartyOIDC(ctx, issuer, clientID, clientSecret, redirectURI, scopes, options...)
if err != nil { if err != nil {
log.Panic().Err(err).Str("issuer", issuer).Str("redirectURI", redirectURI).Strs("scopes", scopes). log.Panic().Err(err).Str("issuer", issuer).Str("redirectURI", redirectURI).Strs("scopes", scopes).
Msg("failed to get new relying party oicd") Msg("failed to initialize new relying party oidc")
} }
return relyingParty return relyingParty
} }
func NewRelyingPartyGithub(config *config.Config, provider string, log log.Logger) rp.RelyingParty { func NewRelyingPartyGithub(config *config.Config, provider string, hashKey, encryptKey []byte, log log.Logger,
_, clientID, clientSecret, redirectURI, scopes, options := getRelyingPartyArgs(config, provider, log) ) rp.RelyingParty {
_, clientID, clientSecret, redirectURI, scopes,
options := getRelyingPartyArgs(config, provider, hashKey, encryptKey, log)
rpConfig := &oauth2.Config{ rpConfig := &oauth2.Config{
ClientID: clientID, ClientID: clientID,
@ -636,13 +642,13 @@ func NewRelyingPartyGithub(config *config.Config, provider string, log log.Logge
relyingParty, err := rp.NewRelyingPartyOAuth(rpConfig, options...) relyingParty, err := rp.NewRelyingPartyOAuth(rpConfig, options...)
if err != nil { if err != nil {
log.Panic().Err(err).Str("redirectURI", redirectURI).Strs("scopes", scopes). log.Panic().Err(err).Str("redirectURI", redirectURI).Strs("scopes", scopes).
Msg("failed to get new relying party oauth") Msg("failed to initialize new relying party oauth")
} }
return relyingParty return relyingParty
} }
func getRelyingPartyArgs(cfg *config.Config, provider string, log log.Logger) ( func getRelyingPartyArgs(cfg *config.Config, provider string, hashKey, encryptKey []byte, log log.Logger) (
string, string, string, string, []string, []rp.Option, string, string, string, string, []string, []rp.Option,
) { ) {
if _, ok := cfg.HTTP.Auth.OpenID.Providers[provider]; !ok { if _, ok := cfg.HTTP.Auth.OpenID.Providers[provider]; !ok {
@ -683,9 +689,8 @@ func getRelyingPartyArgs(cfg *config.Config, provider string, log log.Logger) (
rp.WithVerifierOpts(rp.WithIssuedAtOffset(issuedAtOffset)), rp.WithVerifierOpts(rp.WithIssuedAtOffset(issuedAtOffset)),
} }
key := securecookie.GenerateRandomKey(32) //nolint:mnd cookieHandler := httphelper.NewCookieHandler(hashKey, encryptKey, httphelper.WithMaxAge(relyingPartyCookieMaxAge))
cookieHandler := httphelper.NewCookieHandler(key, key, httphelper.WithMaxAge(relyingPartyCookieMaxAge))
options = append(options, rp.WithCookieHandler(cookieHandler)) options = append(options, rp.WithCookieHandler(cookieHandler))
if clientSecret == "" { if clientSecret == "" {
@ -839,14 +844,14 @@ func OAuth2Callback(ctlr *Controller, w http.ResponseWriter, r *http.Request, st
stateOrigin, ok := stateCookie.Values["state"].(string) stateOrigin, ok := stateCookie.Values["state"].(string)
if !ok { if !ok {
ctlr.Log.Error().Err(zerr.ErrInvalidStateCookie).Str("component", "openID"). ctlr.Log.Error().Err(zerr.ErrInvalidStateCookie).Str("component", "openID").
Msg(": failed to get 'state' cookie from request") Msg("failed to get 'state' cookie from request")
return "", zerr.ErrInvalidStateCookie return "", zerr.ErrInvalidStateCookie
} }
if stateOrigin != state { if stateOrigin != state {
ctlr.Log.Error().Err(zerr.ErrInvalidStateCookie).Str("component", "openID"). ctlr.Log.Error().Err(zerr.ErrInvalidStateCookie).Str("component", "openID").
Msg(": 'state' cookie differs from the actual one") Msg("'state' cookie differs from the actual one")
return "", zerr.ErrInvalidStateCookie return "", zerr.ErrInvalidStateCookie
} }

View file

@ -960,7 +960,7 @@ func TestCookiestoreCleanup(t *testing.T) {
DefaultStore: imgStore, DefaultStore: imgStore,
} }
cookieStore, err := api.NewCookieStore(storeController) cookieStore, err := api.NewCookieStore(storeController, nil, nil)
So(err, ShouldBeNil) So(err, ShouldBeNil)
cookieStore.RunSessionCleaner(taskScheduler) cookieStore.RunSessionCleaner(taskScheduler)
@ -995,7 +995,7 @@ func TestCookiestoreCleanup(t *testing.T) {
DefaultStore: imgStore, DefaultStore: imgStore,
} }
cookieStore, err := api.NewCookieStore(storeController) cookieStore, err := api.NewCookieStore(storeController, []byte("secret"), nil)
So(err, ShouldBeNil) So(err, ShouldBeNil)
err = os.Chmod(rootDir, 0o000) err = os.Chmod(rootDir, 0o000)

View file

@ -73,6 +73,9 @@ type AuthConfig struct {
Bearer *BearerConfig Bearer *BearerConfig
OpenID *OpenIDConfig OpenID *OpenIDConfig
APIKey bool APIKey bool
SessionKeysFile string
SessionHashKey []byte `json:"-"`
SessionEncryptKey []byte `json:"-"`
} }
type BearerConfig struct { type BearerConfig struct {
@ -81,6 +84,11 @@ type BearerConfig struct {
Cert string Cert string
} }
type SessionKeys struct {
HashKey string
EncryptKey string `mapstructure:",omitempty"`
}
type OpenIDConfig struct { type OpenIDConfig struct {
Providers map[string]OpenIDProviderConfig Providers map[string]OpenIDProviderConfig
} }

View file

@ -15,6 +15,7 @@ import (
"time" "time"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/gorilla/securecookie"
"github.com/zitadel/oidc/v3/pkg/client/rp" "github.com/zitadel/oidc/v3/pkg/client/rp"
"zotregistry.dev/zot/errors" "zotregistry.dev/zot/errors"
@ -308,7 +309,14 @@ func (c *Controller) InitImageStore() error {
func (c *Controller) initCookieStore() error { func (c *Controller) initCookieStore() error {
// setup sessions cookie store used to preserve logged in user in web sessions // setup sessions cookie store used to preserve logged in user in web sessions
if c.Config.IsBasicAuthnEnabled() { if c.Config.IsBasicAuthnEnabled() {
cookieStore, err := NewCookieStore(c.StoreController) if c.Config.HTTP.Auth.SessionHashKey == nil {
c.Log.Warn().Msg("hashKey is not set in config, generating a random one")
c.Config.HTTP.Auth.SessionHashKey = securecookie.GenerateRandomKey(64) //nolint: gomnd
}
cookieStore, err := NewCookieStore(c.StoreController, c.Config.HTTP.Auth.SessionHashKey,
c.Config.HTTP.Auth.SessionEncryptKey)
if err != nil { if err != nil {
return err return err
} }

View file

@ -2272,7 +2272,7 @@ func TestAuthnErrors(t *testing.T) {
} }
So(func() { So(func() {
api.NewRelyingPartyGithub(conf, "prov", log.NewLogger("debug", "")) api.NewRelyingPartyGithub(conf, "prov", nil, nil, log.NewLogger("debug", ""))
}, ShouldPanic) }, ShouldPanic)
err = os.Chmod(tmpFile, 0o644) err = os.Chmod(tmpFile, 0o644)
@ -4099,7 +4099,7 @@ func TestNewRelyingPartyOIDC(t *testing.T) {
} }
Convey("provider not found in config", func() { Convey("provider not found in config", func() {
So(func() { _ = api.NewRelyingPartyOIDC(ctx, conf, "notDex", log.NewLogger("debug", "")) }, ShouldPanic) So(func() { _ = api.NewRelyingPartyOIDC(ctx, conf, "notDex", nil, nil, log.NewLogger("debug", "")) }, ShouldPanic)
}) })
Convey("key path not found on disk", func() { Convey("key path not found on disk", func() {
@ -4107,7 +4107,7 @@ func TestNewRelyingPartyOIDC(t *testing.T) {
oidcProviderCfg.KeyPath = "path/to/file" oidcProviderCfg.KeyPath = "path/to/file"
conf.HTTP.Auth.OpenID.Providers["oidc"] = oidcProviderCfg conf.HTTP.Auth.OpenID.Providers["oidc"] = oidcProviderCfg
So(func() { _ = api.NewRelyingPartyOIDC(ctx, conf, "oidc", log.NewLogger("debug", "")) }, ShouldPanic) So(func() { _ = api.NewRelyingPartyOIDC(ctx, conf, "oidc", nil, nil, log.NewLogger("debug", "")) }, ShouldPanic)
}) })
Convey("https callback", func() { Convey("https callback", func() {
@ -4116,7 +4116,7 @@ func TestNewRelyingPartyOIDC(t *testing.T) {
Key: ServerKey, Key: ServerKey,
} }
rp := api.NewRelyingPartyOIDC(ctx, conf, "oidc", log.NewLogger("debug", "")) rp := api.NewRelyingPartyOIDC(ctx, conf, "oidc", nil, nil, log.NewLogger("debug", ""))
So(rp, ShouldNotBeNil) So(rp, ShouldNotBeNil)
}) })
@ -4125,7 +4125,7 @@ func TestNewRelyingPartyOIDC(t *testing.T) {
oidcProvider.ClientSecret = "" oidcProvider.ClientSecret = ""
conf.HTTP.Auth.OpenID.Providers["oidc"] = oidcProvider conf.HTTP.Auth.OpenID.Providers["oidc"] = oidcProvider
rp := api.NewRelyingPartyOIDC(ctx, conf, "oidc", log.NewLogger("debug", "")) rp := api.NewRelyingPartyOIDC(ctx, conf, "oidc", nil, nil, log.NewLogger("debug", ""))
So(rp, ShouldNotBeNil) So(rp, ShouldNotBeNil)
}) })
@ -4134,7 +4134,7 @@ func TestNewRelyingPartyOIDC(t *testing.T) {
oidcProvider.Issuer = "" oidcProvider.Issuer = ""
conf.HTTP.Auth.OpenID.Providers["oidc"] = oidcProvider conf.HTTP.Auth.OpenID.Providers["oidc"] = oidcProvider
So(func() { _ = api.NewRelyingPartyOIDC(ctx, conf, "oidc", log.NewLogger("debug", "")) }, ShouldPanic) So(func() { _ = api.NewRelyingPartyOIDC(ctx, conf, "oidc", nil, nil, log.NewLogger("debug", "")) }, ShouldPanic)
}) })
}) })
} }
@ -4151,6 +4151,7 @@ func TestOpenIDMiddleware(t *testing.T) {
testCaseName string testCaseName string
address string address string
externalURL string externalURL string
useSessionKeys bool
}{ }{
{ {
address: "0.0.0.0", address: "0.0.0.0",
@ -4162,6 +4163,12 @@ func TestOpenIDMiddleware(t *testing.T) {
externalURL: "", externalURL: "",
testCaseName: "without ExternalURL provided in config", testCaseName: "without ExternalURL provided in config",
}, },
{
address: "127.0.0.1",
externalURL: "",
testCaseName: "without ExternalURL provided in config and session keys for cookies",
useSessionKeys: true,
},
} }
// need a username different than ldap one, to test both logic // need a username different than ldap one, to test both logic
@ -4246,6 +4253,11 @@ func TestOpenIDMiddleware(t *testing.T) {
for _, testcase := range testCases { for _, testcase := range testCases {
t.Run(testcase.testCaseName, func(t *testing.T) { t.Run(testcase.testCaseName, func(t *testing.T) {
if testcase.useSessionKeys {
ctlr.Config.HTTP.Auth.SessionHashKey = []byte("3lrioGLGO2RfG9Y7HQGgWa3ayBjMLw2auMXqEWcSXjQKc9SoQ3fKTIbO+toPYa7e")
ctlr.Config.HTTP.Auth.SessionEncryptKey = []byte("KOzt01JrDz2uC//UBC5ZikxQw4owfmI8")
}
Convey("make controller", t, func() { Convey("make controller", t, func() {
dir := t.TempDir() dir := t.TempDir()

View file

@ -11,10 +11,8 @@ import (
"strings" "strings"
"time" "time"
"github.com/gorilla/securecookie"
"github.com/gorilla/sessions" "github.com/gorilla/sessions"
zerr "zotregistry.dev/zot/errors"
"zotregistry.dev/zot/pkg/scheduler" "zotregistry.dev/zot/pkg/scheduler"
"zotregistry.dev/zot/pkg/storage" "zotregistry.dev/zot/pkg/storage"
storageConstants "zotregistry.dev/zot/pkg/storage/constants" storageConstants "zotregistry.dev/zot/pkg/storage/constants"
@ -38,16 +36,11 @@ func (c *CookieStore) RunSessionCleaner(sch *scheduler.Scheduler) {
} }
} }
func NewCookieStore(storeController storage.StoreController) (*CookieStore, error) { func NewCookieStore(storeController storage.StoreController, hashKey, encryptKey []byte) (*CookieStore, error) {
// To store custom types in our cookies // To store custom types in our cookies
// we must first register them using gob.Register // we must first register them using gob.Register
gob.Register(map[string]interface{}{}) gob.Register(map[string]interface{}{})
hashKey, err := getHashKey()
if err != nil {
return &CookieStore{}, err
}
var store sessions.Store var store sessions.Store
var sessionsDir string var sessionsDir string
@ -60,14 +53,14 @@ func NewCookieStore(storeController storage.StoreController) (*CookieStore, erro
return &CookieStore{}, err return &CookieStore{}, err
} }
localStore := sessions.NewFilesystemStore(sessionsDir, hashKey) localStore := sessions.NewFilesystemStore(sessionsDir, hashKey, encryptKey)
localStore.MaxAge(cookiesMaxAge) localStore.MaxAge(cookiesMaxAge)
store = localStore store = localStore
needsCleanup = true needsCleanup = true
} else { } else {
memStore := sessions.NewCookieStore(hashKey) memStore := sessions.NewCookieStore(hashKey, encryptKey)
memStore.MaxAge(cookiesMaxAge) memStore.MaxAge(cookiesMaxAge)
@ -81,15 +74,6 @@ func NewCookieStore(storeController storage.StoreController) (*CookieStore, erro
}, nil }, nil
} }
func getHashKey() ([]byte, error) {
hashKey := securecookie.GenerateRandomKey(64)
if hashKey == nil {
return nil, zerr.ErrHashKeyNotCreated
}
return hashKey, nil
}
func IsExpiredSession(dirEntry fs.DirEntry) bool { func IsExpiredSession(dirEntry fs.DirEntry) bool {
fileInfo, err := dirEntry.Info() fileInfo, err := dirEntry.Info()
if err != nil { // may have been deleted in the meantime if err != nil { // may have been deleted in the meantime

View file

@ -839,6 +839,12 @@ func LoadConfiguration(config *config.Config, configPath string) error {
return err return err
} }
if err := loadSessionKeys(config); err != nil {
log.Error().Err(err).Msg("failed to read sessionKeysFile")
return err
}
// defaults // defaults
applyDefaultValues(config, viperInstance, log) applyDefaultValues(config, viperInstance, log)
@ -853,6 +859,26 @@ func LoadConfiguration(config *config.Config, configPath string) error {
return nil return nil
} }
func loadSessionKeys(conf *config.Config) error {
if conf.HTTP.Auth != nil && conf.HTTP.Auth.SessionKeysFile != "" {
var sessionKeys config.SessionKeys
if err := readSecretFile(conf.HTTP.Auth.SessionKeysFile, &sessionKeys, false); err != nil {
return err
}
if sessionKeys.HashKey != "" {
conf.HTTP.Auth.SessionHashKey = []byte(sessionKeys.HashKey)
}
if sessionKeys.EncryptKey != "" {
conf.HTTP.Auth.SessionEncryptKey = []byte(sessionKeys.EncryptKey)
}
}
return nil
}
func updateLDAPConfig(conf *config.Config) error { func updateLDAPConfig(conf *config.Config) error {
if conf.HTTP.Auth == nil || conf.HTTP.Auth.LDAP == nil { if conf.HTTP.Auth == nil || conf.HTTP.Auth.LDAP == nil {
return nil return nil
@ -864,8 +890,9 @@ func updateLDAPConfig(conf *config.Config) error {
return nil return nil
} }
newLDAPCredentials, err := readLDAPCredentials(conf.HTTP.Auth.LDAP.CredentialsFile) var newLDAPCredentials config.LDAPCredentials
if err != nil {
if err := readSecretFile(conf.HTTP.Auth.LDAP.CredentialsFile, &newLDAPCredentials, true); err != nil {
return err return err
} }
@ -875,48 +902,46 @@ func updateLDAPConfig(conf *config.Config) error {
return nil return nil
} }
func readLDAPCredentials(ldapConfigPath string) (config.LDAPCredentials, error) { func readSecretFile(path string, v any, checkUnsetFields bool) error { //nolint: varnamelen
viperInstance := viper.NewWithOptions(viper.KeyDelimiter("::")) viperInstance := viper.NewWithOptions(viper.KeyDelimiter("::"))
viperInstance.SetConfigFile(ldapConfigPath) viperInstance.SetConfigFile(path)
if err := viperInstance.ReadInConfig(); err != nil { if err := viperInstance.ReadInConfig(); err != nil {
log.Error().Err(err).Msg("failed to read configuration") log.Error().Err(err).Str("path", path).Msg("failed to read secret file configuration")
return config.LDAPCredentials{}, errors.Join(zerr.ErrBadConfig, err) return errors.Join(zerr.ErrBadConfig, err)
} }
var ldapCredentials config.LDAPCredentials
metaData := &mapstructure.Metadata{} metaData := &mapstructure.Metadata{}
if err := viperInstance.Unmarshal(&ldapCredentials, metadataConfig(metaData)); err != nil { if err := viperInstance.Unmarshal(v, metadataConfig(metaData)); err != nil {
log.Error().Err(err).Msg("failed to unmarshal ldap credentials config") log.Error().Err(err).Str("path", path).Msg("failed to unmarshal secret file config")
return config.LDAPCredentials{}, errors.Join(zerr.ErrBadConfig, err) return errors.Join(zerr.ErrBadConfig, err)
} }
if len(metaData.Keys) == 0 { if len(metaData.Keys) == 0 {
log.Error().Err(zerr.ErrBadConfig). log.Error().Err(zerr.ErrBadConfig).Str("path", path).
Msg("failed to load ldap credentials config due to the absence of any key:value pair") Msg("failed to load secret file due to the absence of any key:value pair")
return config.LDAPCredentials{}, zerr.ErrBadConfig return zerr.ErrBadConfig
} }
if len(metaData.Unused) > 0 { if len(metaData.Unused) > 0 {
log.Error().Err(zerr.ErrBadConfig).Strs("keys", metaData.Unused). log.Error().Err(zerr.ErrBadConfig).Str("path", path).Strs("keys", metaData.Unused).
Msg("failed to load ldap credentials config due to unknown keys") Msg("failed to load secret file due to unknown keys")
return config.LDAPCredentials{}, zerr.ErrBadConfig return zerr.ErrBadConfig
} }
if len(metaData.Unset) > 0 { if checkUnsetFields && len(metaData.Unset) > 0 {
log.Error().Err(zerr.ErrBadConfig).Strs("keys", metaData.Unset). log.Error().Err(zerr.ErrBadConfig).Strs("keys", metaData.Unset).
Msg("failed to load ldap credentials config due to unset keys") Msg("failed to load ldap credentials config due to unset keys")
return config.LDAPCredentials{}, zerr.ErrBadConfig return zerr.ErrBadConfig
} }
return ldapCredentials, nil return nil
} }
func authzContainsOnlyAnonymousPolicy(cfg *config.Config) bool { func authzContainsOnlyAnonymousPolicy(cfg *config.Config) bool {

View file

@ -1350,6 +1350,77 @@ storage:
So(err, ShouldBeNil) So(err, ShouldBeNil)
}) })
Convey("Test verify good session keys config with both keys", t, func(c C) {
tmpFile, err := os.CreateTemp("", "zot-test*.json")
So(err, ShouldBeNil)
defer os.Remove(tmpFile.Name())
tmpCredsFile, err := os.CreateTemp("", "zot-cred*.json")
So(err, ShouldBeNil)
defer os.Remove(tmpCredsFile.Name())
content := []byte(`{
"hashKey":"very-secret",
"encryptKey":"another-secret"
}`)
_, err = tmpCredsFile.Write(content)
So(err, ShouldBeNil)
err = tmpCredsFile.Close()
So(err, ShouldBeNil)
content = []byte(fmt.Sprintf(`{ "distSpecVersion": "1.1.0-dev",
"storage": { "rootDirectory": "/tmp/zot" }, "http": { "address": "127.0.0.1", "port": "8080",
"auth":{"htpasswd":{"path":"test/data/htpasswd"}, "sessionKeysFile": "%s",
"failDelay": 5 } }, "log": { "level": "debug" } }`,
tmpCredsFile.Name()),
)
_, err = tmpFile.Write(content)
So(err, ShouldBeNil)
err = tmpFile.Close()
So(err, ShouldBeNil)
os.Args = []string{"cli_test", "verify", tmpFile.Name()}
err = cli.NewServerRootCmd().Execute()
So(err, ShouldBeNil)
})
Convey("Test verify good session keys config with one key", t, func(c C) {
tmpFile, err := os.CreateTemp("", "zot-test*.json")
So(err, ShouldBeNil)
defer os.Remove(tmpFile.Name())
tmpCredsFile, err := os.CreateTemp("", "zot-cred*.json")
So(err, ShouldBeNil)
defer os.Remove(tmpCredsFile.Name())
content := []byte(`{
"hashKey":"very-secret"
}`)
_, err = tmpCredsFile.Write(content)
So(err, ShouldBeNil)
err = tmpCredsFile.Close()
So(err, ShouldBeNil)
content = []byte(fmt.Sprintf(`{ "distSpecVersion": "1.1.0-dev",
"storage": { "rootDirectory": "/tmp/zot" }, "http": { "address": "127.0.0.1", "port": "8080",
"auth":{"htpasswd":{"path":"test/data/htpasswd"}, "sessionKeysFile": "%s",
"failDelay": 5 } }, "log": { "level": "debug" } }`,
tmpCredsFile.Name()),
)
_, err = tmpFile.Write(content)
So(err, ShouldBeNil)
err = tmpFile.Close()
So(err, ShouldBeNil)
os.Args = []string{"cli_test", "verify", tmpFile.Name()}
err = cli.NewServerRootCmd().Execute()
So(err, ShouldBeNil)
})
Convey("Test verify good ldap config", t, func(c C) { Convey("Test verify good ldap config", t, func(c C) {
tmpFile, err := os.CreateTemp("", "zot-test*.json") tmpFile, err := os.CreateTemp("", "zot-test*.json")
So(err, ShouldBeNil) So(err, ShouldBeNil)