mirror of
https://github.com/project-zot/zot.git
synced 2026-06-17 21:17:58 +08:00
dfb5d1df54
* fix: make config read/write thread safe and fix some other similar issues 1. The config config has a lock, and safe methods to update and read the attributes 2. The config has methods to retrieve copies of specific attributes, such as the extyensions config, the auth config, and the authz config. These are needed, as the config object may mutate in the middle of an auth/authz requests, and we avoid partial configuration being applied for that request. 3. Fix an issue with the monitoring server not stopping when the controller is shut down. 4. Fix an issue with the HTPasswdWatcher not stopping when the background tasks are supposed to finish. 5. Fix some tests using hardcoded ports. Moved some of the methods which were on the main config to the auth, access control and extension configs Signed-off-by: Andrei Aaron <andreifdaaron@gmail.com>
3399 lines
101 KiB
Go
3399 lines
101 KiB
Go
package config_test
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
. "github.com/smartystreets/goconvey/convey"
|
|
|
|
"zotregistry.dev/zot/v2/pkg/api/config"
|
|
"zotregistry.dev/zot/v2/pkg/compat"
|
|
extconf "zotregistry.dev/zot/v2/pkg/extensions/config"
|
|
eventsconf "zotregistry.dev/zot/v2/pkg/extensions/config/events"
|
|
syncconf "zotregistry.dev/zot/v2/pkg/extensions/config/sync"
|
|
)
|
|
|
|
func TestConfig(t *testing.T) {
|
|
Convey("Test config utils", t, func() {
|
|
firstStorageConfig := config.StorageConfig{
|
|
GC: true, Dedupe: true,
|
|
GCDelay: 1 * time.Minute, GCInterval: 1 * time.Hour,
|
|
}
|
|
secondStorageConfig := config.StorageConfig{
|
|
GC: true, Dedupe: true,
|
|
GCDelay: 1 * time.Minute, GCInterval: 1 * time.Hour,
|
|
}
|
|
|
|
So(firstStorageConfig.ParamsEqual(secondStorageConfig), ShouldBeTrue)
|
|
|
|
firstStorageConfig.GC = false
|
|
|
|
So(firstStorageConfig.ParamsEqual(secondStorageConfig), ShouldBeFalse)
|
|
|
|
firstStorageConfig.GC = true
|
|
firstStorageConfig.Dedupe = false
|
|
|
|
So(firstStorageConfig.ParamsEqual(secondStorageConfig), ShouldBeFalse)
|
|
|
|
firstStorageConfig.Dedupe = true
|
|
firstStorageConfig.GCDelay = 2 * time.Minute
|
|
|
|
So(firstStorageConfig.ParamsEqual(secondStorageConfig), ShouldBeFalse)
|
|
|
|
firstStorageConfig.GCDelay = 1 * time.Minute
|
|
firstStorageConfig.GCInterval = 2 * time.Hour
|
|
|
|
So(firstStorageConfig.ParamsEqual(secondStorageConfig), ShouldBeFalse)
|
|
|
|
firstStorageConfig.GCInterval = 1 * time.Hour
|
|
|
|
So(firstStorageConfig.ParamsEqual(secondStorageConfig), ShouldBeTrue)
|
|
|
|
isSame, err := config.SameFile("test-config", "test")
|
|
So(err, ShouldNotBeNil)
|
|
So(isSame, ShouldBeFalse)
|
|
|
|
dir1 := t.TempDir()
|
|
|
|
isSame, err = config.SameFile(dir1, "test")
|
|
So(err, ShouldNotBeNil)
|
|
So(isSame, ShouldBeFalse)
|
|
|
|
dir2 := t.TempDir()
|
|
|
|
isSame, err = config.SameFile(dir1, dir2)
|
|
So(err, ShouldBeNil)
|
|
So(isSame, ShouldBeFalse)
|
|
|
|
isSame, err = config.SameFile(dir1, dir1)
|
|
So(err, ShouldBeNil)
|
|
So(isSame, ShouldBeTrue)
|
|
})
|
|
|
|
Convey("Test DeepCopy() & Sanitize()", t, func() {
|
|
Convey("Test DeepCopy negative cases", func() {
|
|
conf := config.New()
|
|
So(conf, ShouldNotBeNil)
|
|
|
|
// negative
|
|
obj := make(chan int)
|
|
err := config.DeepCopy(conf, obj)
|
|
So(err, ShouldNotBeNil)
|
|
err = config.DeepCopy(obj, conf)
|
|
So(err, ShouldNotBeNil)
|
|
})
|
|
|
|
Convey("Test Sanitize() with LDAP bind password", func() {
|
|
conf := config.New()
|
|
So(conf, ShouldNotBeNil)
|
|
|
|
// Set LDAP bind password
|
|
authConfig := &config.AuthConfig{LDAP: (&config.LDAPConfig{}).SetBindPassword("secret-ldap-password")}
|
|
conf.HTTP.Auth = authConfig
|
|
|
|
So(func() { conf.Sanitize() }, ShouldNotPanic)
|
|
|
|
sanitizedConf := conf.Sanitize()
|
|
So(sanitizedConf.HTTP.Auth.LDAP.BindPassword(), ShouldEqual, "******")
|
|
|
|
// Verify original config is not modified
|
|
So(conf.HTTP.Auth.LDAP.BindPassword(), ShouldEqual, "secret-ldap-password")
|
|
})
|
|
|
|
Convey("Test Sanitize() with OpenID client secrets", func() {
|
|
conf := config.New()
|
|
So(conf, ShouldNotBeNil)
|
|
|
|
// Set OpenID client secrets
|
|
authConfig := &config.AuthConfig{
|
|
OpenID: &config.OpenIDConfig{
|
|
Providers: map[string]config.OpenIDProviderConfig{
|
|
"google": {
|
|
Name: "Google",
|
|
ClientID: "google-client-id",
|
|
ClientSecret: "google-client-secret",
|
|
Issuer: "https://accounts.google.com",
|
|
Scopes: []string{"openid", "email"},
|
|
},
|
|
"github": {
|
|
Name: "GitHub",
|
|
ClientID: "github-client-id",
|
|
ClientSecret: "github-client-secret",
|
|
Scopes: []string{"user:email"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
conf.HTTP.Auth = authConfig
|
|
|
|
So(func() { conf.Sanitize() }, ShouldNotPanic)
|
|
|
|
sanitizedConf := conf.Sanitize()
|
|
|
|
// Verify OpenID client secrets are sanitized
|
|
So(sanitizedConf.HTTP.Auth.OpenID.Providers["google"].ClientSecret, ShouldEqual, "******")
|
|
So(sanitizedConf.HTTP.Auth.OpenID.Providers["github"].ClientSecret, ShouldEqual, "******")
|
|
|
|
// Verify other fields are preserved
|
|
So(sanitizedConf.HTTP.Auth.OpenID.Providers["google"].ClientID, ShouldEqual, "google-client-id")
|
|
So(sanitizedConf.HTTP.Auth.OpenID.Providers["google"].Name, ShouldEqual, "Google")
|
|
So(sanitizedConf.HTTP.Auth.OpenID.Providers["google"].Issuer, ShouldEqual, "https://accounts.google.com")
|
|
So(sanitizedConf.HTTP.Auth.OpenID.Providers["google"].Scopes, ShouldResemble, []string{"openid", "email"})
|
|
|
|
// Verify original config is not modified
|
|
So(conf.HTTP.Auth.OpenID.Providers["google"].ClientSecret, ShouldEqual, "google-client-secret")
|
|
So(conf.HTTP.Auth.OpenID.Providers["github"].ClientSecret, ShouldEqual, "github-client-secret")
|
|
})
|
|
|
|
Convey("Test Sanitize() with Event sink credentials", func() {
|
|
conf := config.New()
|
|
So(conf, ShouldNotBeNil)
|
|
|
|
// Enable events extension and set sink credentials
|
|
enabled := true
|
|
conf.Extensions = &extconf.ExtensionConfig{
|
|
Events: &eventsconf.Config{
|
|
Enable: &enabled,
|
|
Sinks: []eventsconf.SinkConfig{
|
|
{
|
|
Type: eventsconf.HTTP,
|
|
Address: "https://example.com/webhook",
|
|
Credentials: &eventsconf.Credentials{
|
|
Username: "webhook-user",
|
|
Password: "webhook-password",
|
|
},
|
|
},
|
|
{
|
|
Type: eventsconf.NATS,
|
|
Address: "nats://localhost:4222",
|
|
Credentials: &eventsconf.Credentials{
|
|
Username: "nats-user",
|
|
Password: "nats-token",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
So(func() { conf.Sanitize() }, ShouldNotPanic)
|
|
|
|
sanitizedConf := conf.Sanitize()
|
|
|
|
// Verify event sink credentials passwords are sanitized
|
|
So(sanitizedConf.Extensions.Events.Sinks[0].Credentials.Password, ShouldEqual, "******")
|
|
So(sanitizedConf.Extensions.Events.Sinks[1].Credentials.Password, ShouldEqual, "******")
|
|
|
|
// Verify other fields are preserved
|
|
So(sanitizedConf.Extensions.Events.Sinks[0].Credentials.Username, ShouldEqual, "webhook-user")
|
|
So(sanitizedConf.Extensions.Events.Sinks[1].Credentials.Username, ShouldEqual, "nats-user")
|
|
So(sanitizedConf.Extensions.Events.Sinks[0].Type, ShouldEqual, eventsconf.HTTP)
|
|
So(sanitizedConf.Extensions.Events.Sinks[1].Type, ShouldEqual, eventsconf.NATS)
|
|
|
|
// Verify original config is not modified
|
|
So(conf.Extensions.Events.Sinks[0].Credentials.Password, ShouldEqual, "webhook-password")
|
|
So(conf.Extensions.Events.Sinks[1].Credentials.Password, ShouldEqual, "nats-token")
|
|
})
|
|
|
|
Convey("Test Sanitize() with Event sink credentials including nil credentials", func() {
|
|
conf := config.New()
|
|
So(conf, ShouldNotBeNil)
|
|
|
|
// Enable events extension with mixed sink credentials (some nil, some not)
|
|
enabled := true
|
|
conf.Extensions = &extconf.ExtensionConfig{
|
|
Events: &eventsconf.Config{
|
|
Enable: &enabled,
|
|
Sinks: []eventsconf.SinkConfig{
|
|
{
|
|
Type: eventsconf.HTTP,
|
|
Address: "https://example.com/webhook",
|
|
Credentials: &eventsconf.Credentials{
|
|
Username: "webhook-user",
|
|
Password: "webhook-password",
|
|
},
|
|
},
|
|
{
|
|
Type: eventsconf.NATS,
|
|
Address: "nats://localhost:4222",
|
|
Credentials: nil, // This should trigger the continue statement
|
|
},
|
|
{
|
|
Type: eventsconf.HTTP,
|
|
Address: "https://another.com/webhook",
|
|
Credentials: &eventsconf.Credentials{
|
|
Username: "another-user",
|
|
Password: "another-password",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
So(func() { conf.Sanitize() }, ShouldNotPanic)
|
|
|
|
sanitizedConf := conf.Sanitize()
|
|
|
|
// Verify that sinks with credentials have their passwords sanitized
|
|
So(sanitizedConf.Extensions.Events.Sinks[0].Credentials.Password, ShouldEqual, "******")
|
|
So(sanitizedConf.Extensions.Events.Sinks[2].Credentials.Password, ShouldEqual, "******")
|
|
|
|
// Verify that sink with nil credentials is preserved as-is (no panic, no modification)
|
|
So(sanitizedConf.Extensions.Events.Sinks[1].Credentials, ShouldBeNil)
|
|
So(sanitizedConf.Extensions.Events.Sinks[1].Type, ShouldEqual, eventsconf.NATS)
|
|
So(sanitizedConf.Extensions.Events.Sinks[1].Address, ShouldEqual, "nats://localhost:4222")
|
|
|
|
// Verify other fields are preserved
|
|
So(sanitizedConf.Extensions.Events.Sinks[0].Credentials.Username, ShouldEqual, "webhook-user")
|
|
So(sanitizedConf.Extensions.Events.Sinks[2].Credentials.Username, ShouldEqual, "another-user")
|
|
So(sanitizedConf.Extensions.Events.Sinks[0].Type, ShouldEqual, eventsconf.HTTP)
|
|
So(sanitizedConf.Extensions.Events.Sinks[2].Type, ShouldEqual, eventsconf.HTTP)
|
|
|
|
// Verify original config is not modified
|
|
So(conf.Extensions.Events.Sinks[0].Credentials.Password, ShouldEqual, "webhook-password")
|
|
So(conf.Extensions.Events.Sinks[2].Credentials.Password, ShouldEqual, "another-password")
|
|
So(conf.Extensions.Events.Sinks[1].Credentials, ShouldBeNil)
|
|
})
|
|
|
|
Convey("Test Sanitize() with all sensitive data types", func() {
|
|
conf := config.New()
|
|
So(conf, ShouldNotBeNil)
|
|
|
|
// Set all types of sensitive data
|
|
authConfig := &config.AuthConfig{
|
|
LDAP: (&config.LDAPConfig{}).SetBindPassword("ldap-secret"),
|
|
OpenID: &config.OpenIDConfig{
|
|
Providers: map[string]config.OpenIDProviderConfig{
|
|
"azure": {
|
|
Name: "Azure AD",
|
|
ClientID: "azure-client-id",
|
|
ClientSecret: "azure-client-secret",
|
|
Issuer: "https://login.microsoftonline.com/...",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
conf.HTTP.Auth = authConfig
|
|
|
|
// Enable events extension
|
|
enabled := true
|
|
conf.Extensions = &extconf.ExtensionConfig{
|
|
Events: &eventsconf.Config{
|
|
Enable: &enabled,
|
|
Sinks: []eventsconf.SinkConfig{
|
|
{
|
|
Type: eventsconf.HTTP,
|
|
Address: "https://smtp.example.com/webhook",
|
|
Credentials: &eventsconf.Credentials{
|
|
Username: "smtp-user",
|
|
Password: "smtp-password",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
So(func() { conf.Sanitize() }, ShouldNotPanic)
|
|
|
|
sanitizedConf := conf.Sanitize()
|
|
|
|
// Verify all sensitive data is sanitized
|
|
So(sanitizedConf.HTTP.Auth.LDAP.BindPassword(), ShouldEqual, "******")
|
|
So(sanitizedConf.HTTP.Auth.OpenID.Providers["azure"].ClientSecret, ShouldEqual, "******")
|
|
So(sanitizedConf.Extensions.Events.Sinks[0].Credentials.Password, ShouldEqual, "******")
|
|
|
|
// Verify non-sensitive data is preserved
|
|
So(sanitizedConf.HTTP.Auth.OpenID.Providers["azure"].ClientID, ShouldEqual, "azure-client-id")
|
|
So(sanitizedConf.HTTP.Auth.OpenID.Providers["azure"].Name, ShouldEqual, "Azure AD")
|
|
So(sanitizedConf.Extensions.Events.Sinks[0].Credentials.Username, ShouldEqual, "smtp-user")
|
|
So(sanitizedConf.Extensions.Events.Sinks[0].Type, ShouldEqual, eventsconf.HTTP)
|
|
})
|
|
|
|
Convey("Test Sanitize() with nil sensitive data", func() {
|
|
conf := config.New()
|
|
So(conf, ShouldNotBeNil)
|
|
|
|
// Set config with nil sensitive data
|
|
authConfig := &config.AuthConfig{
|
|
LDAP: nil, // No LDAP config
|
|
OpenID: nil, // No OpenID config
|
|
}
|
|
conf.HTTP.Auth = authConfig
|
|
|
|
// No events extension
|
|
conf.Extensions = nil
|
|
|
|
So(func() { conf.Sanitize() }, ShouldNotPanic)
|
|
|
|
sanitizedConf := conf.Sanitize()
|
|
|
|
// Verify nil configs are handled gracefully
|
|
So(sanitizedConf.HTTP.Auth.LDAP, ShouldBeNil)
|
|
So(sanitizedConf.HTTP.Auth.OpenID, ShouldBeNil)
|
|
So(sanitizedConf.Extensions, ShouldBeNil)
|
|
})
|
|
|
|
Convey("Test Sanitize() with empty sensitive data", func() {
|
|
conf := config.New()
|
|
So(conf, ShouldNotBeNil)
|
|
|
|
// Set config with empty sensitive data
|
|
authConfig := &config.AuthConfig{
|
|
LDAP: (&config.LDAPConfig{}).SetBindPassword(""), // Empty password
|
|
OpenID: &config.OpenIDConfig{
|
|
Providers: map[string]config.OpenIDProviderConfig{
|
|
"empty": {
|
|
Name: "Empty Provider",
|
|
ClientID: "empty-client-id",
|
|
ClientSecret: "", // Empty secret
|
|
},
|
|
},
|
|
},
|
|
}
|
|
conf.HTTP.Auth = authConfig
|
|
|
|
// Enable events extension with empty password
|
|
enabled := true
|
|
conf.Extensions = &extconf.ExtensionConfig{
|
|
Events: &eventsconf.Config{
|
|
Enable: &enabled,
|
|
Sinks: []eventsconf.SinkConfig{
|
|
{
|
|
Type: eventsconf.HTTP,
|
|
Address: "https://example.com/webhook",
|
|
Credentials: &eventsconf.Credentials{
|
|
Username: "user",
|
|
Password: "", // Empty password
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
So(func() { conf.Sanitize() }, ShouldNotPanic)
|
|
|
|
sanitizedConf := conf.Sanitize()
|
|
|
|
// Verify empty passwords behavior
|
|
// LDAP empty password should remain empty
|
|
So(sanitizedConf.HTTP.Auth.LDAP.BindPassword(), ShouldEqual, "")
|
|
// OpenID empty secret is always sanitized
|
|
So(sanitizedConf.HTTP.Auth.OpenID.Providers["empty"].ClientSecret, ShouldEqual, "******")
|
|
// Event sink empty password is always sanitized
|
|
So(sanitizedConf.Extensions.Events.Sinks[0].Credentials.Password, ShouldEqual, "******")
|
|
})
|
|
|
|
Convey("Test Sanitize() with nil config", func() {
|
|
var conf *config.Config = nil
|
|
|
|
So(func() { conf.Sanitize() }, ShouldNotPanic)
|
|
|
|
sanitizedConf := conf.Sanitize()
|
|
So(sanitizedConf, ShouldBeNil)
|
|
})
|
|
})
|
|
|
|
Convey("Test IsRetentionEnabled()", t, func() {
|
|
// Test nil config
|
|
var nilConf *config.Config = nil
|
|
|
|
So(nilConf.IsRetentionEnabled(), ShouldBeFalse)
|
|
|
|
conf := config.New()
|
|
So(conf.IsRetentionEnabled(), ShouldBeFalse)
|
|
|
|
conf.Storage.Retention.Policies = []config.RetentionPolicy{
|
|
{
|
|
Repositories: []string{"repo"},
|
|
},
|
|
}
|
|
|
|
So(conf.IsRetentionEnabled(), ShouldBeFalse)
|
|
|
|
policies := []config.RetentionPolicy{
|
|
{
|
|
Repositories: []string{"repo"},
|
|
KeepTags: []config.KeepTagsPolicy{
|
|
{
|
|
Patterns: []string{"tag"},
|
|
MostRecentlyPulledCount: 2,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
conf.Storage.Retention = config.ImageRetention{
|
|
Policies: policies,
|
|
}
|
|
|
|
So(conf.IsRetentionEnabled(), ShouldBeTrue)
|
|
|
|
subPaths := make(map[string]config.StorageConfig)
|
|
|
|
subPaths["/a"] = config.StorageConfig{
|
|
GC: true,
|
|
Retention: config.ImageRetention{
|
|
Policies: policies,
|
|
},
|
|
}
|
|
|
|
conf.Storage.SubPaths = subPaths
|
|
|
|
So(conf.IsRetentionEnabled(), ShouldBeTrue)
|
|
|
|
// Test MostRecentlyPushedCount
|
|
conf = config.New()
|
|
conf.Storage.Retention.Policies = []config.RetentionPolicy{
|
|
{
|
|
Repositories: []string{"repo"},
|
|
KeepTags: []config.KeepTagsPolicy{
|
|
{
|
|
Patterns: []string{"tag"},
|
|
MostRecentlyPushedCount: 3,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
So(conf.IsRetentionEnabled(), ShouldBeTrue)
|
|
|
|
// Test PulledWithin
|
|
conf = config.New()
|
|
duration := time.Hour * 24
|
|
conf.Storage.Retention.Policies = []config.RetentionPolicy{
|
|
{
|
|
Repositories: []string{"repo"},
|
|
KeepTags: []config.KeepTagsPolicy{
|
|
{
|
|
Patterns: []string{"tag"},
|
|
PulledWithin: &duration,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
So(conf.IsRetentionEnabled(), ShouldBeTrue)
|
|
|
|
// Test PushedWithin
|
|
conf = config.New()
|
|
conf.Storage.Retention.Policies = []config.RetentionPolicy{
|
|
{
|
|
Repositories: []string{"repo"},
|
|
KeepTags: []config.KeepTagsPolicy{
|
|
{
|
|
Patterns: []string{"tag"},
|
|
PushedWithin: &duration,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
So(conf.IsRetentionEnabled(), ShouldBeTrue)
|
|
|
|
// Test SubPaths with retention policies
|
|
conf = config.New()
|
|
conf.Storage.SubPaths = map[string]config.StorageConfig{
|
|
"subpath1": {
|
|
Retention: config.ImageRetention{
|
|
Policies: []config.RetentionPolicy{
|
|
{
|
|
Repositories: []string{"repo1"},
|
|
KeepTags: []config.KeepTagsPolicy{
|
|
{
|
|
Patterns: []string{"latest"},
|
|
MostRecentlyPulledCount: 5,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
So(conf.IsRetentionEnabled(), ShouldBeTrue)
|
|
|
|
// Test empty policies with no retention criteria
|
|
conf = config.New()
|
|
conf.Storage.Retention.Policies = []config.RetentionPolicy{
|
|
{
|
|
Repositories: []string{"repo"},
|
|
KeepTags: []config.KeepTagsPolicy{
|
|
{
|
|
Patterns: []string{"tag"},
|
|
// No retention criteria set
|
|
},
|
|
},
|
|
},
|
|
}
|
|
So(conf.IsRetentionEnabled(), ShouldBeFalse)
|
|
})
|
|
|
|
Convey("Test IsEventRecorderEnabled()", t, func() {
|
|
conf := config.New()
|
|
extensionsConfig := conf.CopyExtensionsConfig()
|
|
So(extensionsConfig.IsEventRecorderEnabled(), ShouldBeFalse)
|
|
|
|
// Enable the event recorder
|
|
enable := true
|
|
conf.Extensions = &extconf.ExtensionConfig{}
|
|
conf.Extensions.Events = &eventsconf.Config{
|
|
Enable: &enable,
|
|
}
|
|
|
|
extensionsConfig = conf.CopyExtensionsConfig()
|
|
So(extensionsConfig.IsEventRecorderEnabled(), ShouldBeTrue)
|
|
|
|
// Disabled scenario
|
|
disable := false
|
|
conf.Extensions.Events.Enable = &disable
|
|
extensionsConfig = conf.CopyExtensionsConfig()
|
|
So(extensionsConfig.IsEventRecorderEnabled(), ShouldBeFalse)
|
|
|
|
// nil pointers
|
|
conf.Extensions.Events = nil
|
|
extensionsConfig = conf.CopyExtensionsConfig()
|
|
So(extensionsConfig.IsEventRecorderEnabled(), ShouldBeFalse)
|
|
|
|
conf.Extensions = nil
|
|
extensionsConfig = conf.CopyExtensionsConfig()
|
|
So(extensionsConfig.IsEventRecorderEnabled(), ShouldBeFalse)
|
|
})
|
|
|
|
Convey("Test AccessControlConfig.ContainsOnlyAnonymousPolicy()", t, func() {
|
|
Convey("When accessControlConfig is nil", func() {
|
|
var accessControlConfig *config.AccessControlConfig = nil
|
|
|
|
result := accessControlConfig.ContainsOnlyAnonymousPolicy()
|
|
So(result, ShouldBeTrue)
|
|
})
|
|
|
|
Convey("When accessControlConfig has admin policies", func() {
|
|
accessControlConfig := &config.AccessControlConfig{}
|
|
accessControlConfig.AdminPolicy = config.Policy{
|
|
Actions: []string{"read"},
|
|
Users: []string{"admin"},
|
|
}
|
|
|
|
result := accessControlConfig.ContainsOnlyAnonymousPolicy()
|
|
So(result, ShouldBeFalse)
|
|
})
|
|
|
|
Convey("When accessControlConfig has only anonymous policies", func() {
|
|
accessControlConfig := &config.AccessControlConfig{}
|
|
accessControlConfig.Repositories = config.Repositories{
|
|
"repo1": config.PolicyGroup{
|
|
AnonymousPolicy: []string{"read"},
|
|
},
|
|
}
|
|
|
|
result := accessControlConfig.ContainsOnlyAnonymousPolicy()
|
|
So(result, ShouldBeTrue)
|
|
})
|
|
|
|
Convey("When accessControlConfig has default policies", func() {
|
|
accessControlConfig := &config.AccessControlConfig{}
|
|
accessControlConfig.Repositories = config.Repositories{
|
|
"repo1": config.PolicyGroup{
|
|
DefaultPolicy: []string{"read"},
|
|
},
|
|
}
|
|
|
|
result := accessControlConfig.ContainsOnlyAnonymousPolicy()
|
|
So(result, ShouldBeFalse)
|
|
})
|
|
|
|
Convey("When accessControlConfig has non-empty repository policies", func() {
|
|
accessControlConfig := &config.AccessControlConfig{}
|
|
accessControlConfig.Repositories = config.Repositories{
|
|
"repo1": config.PolicyGroup{
|
|
Policies: []config.Policy{
|
|
{
|
|
Actions: []string{"read"},
|
|
Users: []string{"user1"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
result := accessControlConfig.ContainsOnlyAnonymousPolicy()
|
|
So(result, ShouldBeFalse)
|
|
})
|
|
|
|
Convey("When accessControlConfig has empty admin policy and no repositories", func() {
|
|
accessControlConfig := &config.AccessControlConfig{}
|
|
accessControlConfig.AdminPolicy = config.Policy{
|
|
Actions: []string{},
|
|
Users: []string{},
|
|
}
|
|
accessControlConfig.Repositories = config.Repositories{}
|
|
|
|
result := accessControlConfig.ContainsOnlyAnonymousPolicy()
|
|
So(result, ShouldBeFalse)
|
|
})
|
|
|
|
Convey("When accessControlConfig has empty policies in repository", func() {
|
|
accessControlConfig := &config.AccessControlConfig{}
|
|
accessControlConfig.Repositories = config.Repositories{
|
|
"repo1": config.PolicyGroup{
|
|
AnonymousPolicy: []string{"read"},
|
|
Policies: []config.Policy{
|
|
{
|
|
Actions: []string{},
|
|
Users: []string{},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
result := accessControlConfig.ContainsOnlyAnonymousPolicy()
|
|
So(result, ShouldBeTrue)
|
|
})
|
|
})
|
|
|
|
Convey("Test AuthConfig methods", t, func() {
|
|
Convey("Test IsLdapAuthEnabled()", func() {
|
|
// Test with nil AuthConfig
|
|
var authConfig *config.AuthConfig = nil
|
|
|
|
So(authConfig.IsLdapAuthEnabled(), ShouldBeFalse)
|
|
|
|
// Test with AuthConfig but nil LDAP
|
|
authConfig = &config.AuthConfig{}
|
|
So(authConfig.IsLdapAuthEnabled(), ShouldBeFalse)
|
|
|
|
// Test with AuthConfig and LDAP configured
|
|
authConfig = &config.AuthConfig{
|
|
LDAP: &config.LDAPConfig{},
|
|
}
|
|
So(authConfig.IsLdapAuthEnabled(), ShouldBeTrue)
|
|
})
|
|
|
|
Convey("Test IsHtpasswdAuthEnabled()", func() {
|
|
// Test with nil AuthConfig
|
|
var authConfig *config.AuthConfig = nil
|
|
|
|
So(authConfig.IsHtpasswdAuthEnabled(), ShouldBeFalse)
|
|
|
|
// Test with AuthConfig but empty HTPasswd path
|
|
authConfig = &config.AuthConfig{
|
|
HTPasswd: config.AuthHTPasswd{Path: ""},
|
|
}
|
|
So(authConfig.IsHtpasswdAuthEnabled(), ShouldBeFalse)
|
|
|
|
// Test with AuthConfig and HTPasswd configured
|
|
authConfig = &config.AuthConfig{
|
|
HTPasswd: config.AuthHTPasswd{Path: "/path/to/htpasswd"},
|
|
}
|
|
So(authConfig.IsHtpasswdAuthEnabled(), ShouldBeTrue)
|
|
})
|
|
|
|
Convey("Test IsBearerAuthEnabled()", func() {
|
|
// Test with nil AuthConfig
|
|
var authConfig *config.AuthConfig = nil
|
|
|
|
So(authConfig.IsBearerAuthEnabled(), ShouldBeFalse)
|
|
|
|
// Test with AuthConfig but nil Bearer
|
|
authConfig = &config.AuthConfig{}
|
|
So(authConfig.IsBearerAuthEnabled(), ShouldBeFalse)
|
|
|
|
// Test with AuthConfig and Bearer configured with all required fields
|
|
authConfig = &config.AuthConfig{
|
|
Bearer: &config.BearerConfig{
|
|
Cert: "/path/to/cert.pem",
|
|
Realm: "test-realm",
|
|
Service: "test-service",
|
|
},
|
|
}
|
|
So(authConfig.IsBearerAuthEnabled(), ShouldBeTrue)
|
|
})
|
|
|
|
Convey("Test IsOpenIDAuthEnabled()", func() {
|
|
// Test with nil AuthConfig
|
|
var authConfig *config.AuthConfig = nil
|
|
|
|
So(authConfig.IsOpenIDAuthEnabled(), ShouldBeFalse)
|
|
|
|
// Test with AuthConfig but nil OpenID
|
|
authConfig = &config.AuthConfig{}
|
|
So(authConfig.IsOpenIDAuthEnabled(), ShouldBeFalse)
|
|
|
|
// Test with AuthConfig and OpenID configured with providers
|
|
authConfig = &config.AuthConfig{
|
|
OpenID: &config.OpenIDConfig{
|
|
Providers: map[string]config.OpenIDProviderConfig{
|
|
"google": {
|
|
ClientID: "client-id",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
So(authConfig.IsOpenIDAuthEnabled(), ShouldBeTrue)
|
|
})
|
|
|
|
Convey("Test IsAPIKeyEnabled()", func() {
|
|
// Test with nil AuthConfig
|
|
var authConfig *config.AuthConfig = nil
|
|
|
|
So(authConfig.IsAPIKeyEnabled(), ShouldBeFalse)
|
|
|
|
// Test with AuthConfig but APIKey disabled
|
|
authConfig = &config.AuthConfig{
|
|
APIKey: false,
|
|
}
|
|
So(authConfig.IsAPIKeyEnabled(), ShouldBeFalse)
|
|
|
|
// Test with AuthConfig and APIKey enabled
|
|
authConfig = &config.AuthConfig{
|
|
APIKey: true,
|
|
}
|
|
So(authConfig.IsAPIKeyEnabled(), ShouldBeTrue)
|
|
})
|
|
|
|
Convey("Test IsBasicAuthnEnabled()", func() {
|
|
// Test with nil AuthConfig
|
|
var authConfig *config.AuthConfig = nil
|
|
|
|
So(authConfig.IsBasicAuthnEnabled(), ShouldBeFalse)
|
|
|
|
// Test with AuthConfig but no basic auth methods
|
|
authConfig = &config.AuthConfig{}
|
|
So(authConfig.IsBasicAuthnEnabled(), ShouldBeFalse)
|
|
|
|
// Test with HTPasswd enabled
|
|
authConfig = &config.AuthConfig{
|
|
HTPasswd: config.AuthHTPasswd{Path: "/path/to/htpasswd"},
|
|
}
|
|
So(authConfig.IsBasicAuthnEnabled(), ShouldBeTrue)
|
|
|
|
// Test with LDAP enabled
|
|
authConfig = &config.AuthConfig{
|
|
LDAP: &config.LDAPConfig{},
|
|
}
|
|
So(authConfig.IsBasicAuthnEnabled(), ShouldBeTrue)
|
|
|
|
// Test with OpenID enabled (with ClientID)
|
|
authConfig = &config.AuthConfig{
|
|
OpenID: &config.OpenIDConfig{
|
|
Providers: map[string]config.OpenIDProviderConfig{
|
|
"google": {
|
|
ClientID: "client-id",
|
|
Scopes: []string{"openid", "email"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
So(authConfig.IsBasicAuthnEnabled(), ShouldBeTrue)
|
|
|
|
// Test with OpenID enabled (with Issuer)
|
|
authConfig = &config.AuthConfig{
|
|
OpenID: &config.OpenIDConfig{
|
|
Providers: map[string]config.OpenIDProviderConfig{
|
|
"google": {
|
|
ClientID: "",
|
|
Issuer: "https://accounts.google.com",
|
|
Scopes: []string{},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
So(authConfig.IsBasicAuthnEnabled(), ShouldBeTrue)
|
|
|
|
// Test with OpenID enabled (with Scopes only)
|
|
authConfig = &config.AuthConfig{
|
|
OpenID: &config.OpenIDConfig{
|
|
Providers: map[string]config.OpenIDProviderConfig{
|
|
"google": {
|
|
ClientID: "",
|
|
Issuer: "",
|
|
Scopes: []string{"openid", "email"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
So(authConfig.IsBasicAuthnEnabled(), ShouldBeTrue)
|
|
|
|
// Test with OAuth2 provider (github)
|
|
authConfig = &config.AuthConfig{
|
|
OpenID: &config.OpenIDConfig{
|
|
Providers: map[string]config.OpenIDProviderConfig{
|
|
"github": {
|
|
ClientID: "github-client-id",
|
|
Scopes: []string{"user:email"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
So(authConfig.IsBasicAuthnEnabled(), ShouldBeTrue)
|
|
|
|
// Test with OpenID but no valid providers (empty config)
|
|
// Note: AuthConfig.IsOpenIDAuthEnabled() only checks if provider is supported,
|
|
// not if the configuration is valid, so this returns true
|
|
authConfig = &config.AuthConfig{
|
|
OpenID: &config.OpenIDConfig{
|
|
Providers: map[string]config.OpenIDProviderConfig{
|
|
"google": {
|
|
ClientID: "",
|
|
Issuer: "",
|
|
Scopes: []string{},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
So(authConfig.IsBasicAuthnEnabled(), ShouldBeTrue)
|
|
|
|
// Test with OpenID but unsupported provider
|
|
authConfig = &config.AuthConfig{
|
|
OpenID: &config.OpenIDConfig{
|
|
Providers: map[string]config.OpenIDProviderConfig{
|
|
"unsupported": {
|
|
ClientID: "client-id",
|
|
Scopes: []string{"scope"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
So(authConfig.IsBasicAuthnEnabled(), ShouldBeFalse)
|
|
|
|
// Test with APIKey enabled
|
|
authConfig = &config.AuthConfig{
|
|
APIKey: true,
|
|
}
|
|
So(authConfig.IsBasicAuthnEnabled(), ShouldBeTrue)
|
|
})
|
|
|
|
Convey("Test GetFailDelay()", func() {
|
|
// Test with nil AuthConfig
|
|
var authConfig *config.AuthConfig = nil
|
|
|
|
So(authConfig.GetFailDelay(), ShouldEqual, 0)
|
|
|
|
// Test with AuthConfig and custom FailDelay
|
|
authConfig = &config.AuthConfig{
|
|
FailDelay: 5,
|
|
}
|
|
So(authConfig.GetFailDelay(), ShouldEqual, 5)
|
|
})
|
|
})
|
|
|
|
Convey("Test LDAPConfig methods", t, func() {
|
|
Convey("Test BindDN()", func() {
|
|
ldapConfig := &config.LDAPConfig{}
|
|
So(ldapConfig.BindDN(), ShouldEqual, "")
|
|
|
|
ldapConfig.SetBindDN("cn=admin,dc=example,dc=com")
|
|
So(ldapConfig.BindDN(), ShouldEqual, "cn=admin,dc=example,dc=com")
|
|
})
|
|
|
|
Convey("Test BindPassword()", func() {
|
|
ldapConfig := &config.LDAPConfig{}
|
|
So(ldapConfig.BindPassword(), ShouldEqual, "")
|
|
|
|
ldapConfig.SetBindPassword("secretpassword")
|
|
So(ldapConfig.BindPassword(), ShouldEqual, "secretpassword")
|
|
})
|
|
})
|
|
|
|
Convey("Test AccessControlConfig methods", t, func() {
|
|
Convey("Test IsAuthzEnabled()", func() {
|
|
// Test with nil AccessControlConfig
|
|
var accessControlConfig *config.AccessControlConfig = nil
|
|
|
|
So(accessControlConfig.IsAuthzEnabled(), ShouldBeFalse)
|
|
|
|
// Test with AccessControlConfig
|
|
accessControlConfig = &config.AccessControlConfig{}
|
|
So(accessControlConfig.IsAuthzEnabled(), ShouldBeTrue)
|
|
})
|
|
|
|
Convey("Test AnonymousPolicyExists()", func() {
|
|
// Test with nil AccessControlConfig
|
|
var accessControlConfig *config.AccessControlConfig = nil
|
|
|
|
So(accessControlConfig.AnonymousPolicyExists(), ShouldBeFalse)
|
|
|
|
// Test with AccessControlConfig but no repositories
|
|
accessControlConfig = &config.AccessControlConfig{}
|
|
So(accessControlConfig.AnonymousPolicyExists(), ShouldBeFalse)
|
|
|
|
// Test with AccessControlConfig and repository with anonymous policy
|
|
accessControlConfig = &config.AccessControlConfig{}
|
|
accessControlConfig.Repositories = config.Repositories{
|
|
"repo1": config.PolicyGroup{
|
|
AnonymousPolicy: []string{"read"},
|
|
},
|
|
}
|
|
So(accessControlConfig.AnonymousPolicyExists(), ShouldBeTrue)
|
|
|
|
// Test with AccessControlConfig and repository without anonymous policy
|
|
accessControlConfig = &config.AccessControlConfig{}
|
|
accessControlConfig.Repositories = config.Repositories{
|
|
"repo1": config.PolicyGroup{
|
|
DefaultPolicy: []string{"read"},
|
|
},
|
|
}
|
|
So(accessControlConfig.AnonymousPolicyExists(), ShouldBeFalse)
|
|
})
|
|
|
|
Convey("Test GetRepositories()", func() {
|
|
repositories := config.Repositories{
|
|
"repo1": config.PolicyGroup{
|
|
AnonymousPolicy: []string{"read"},
|
|
},
|
|
}
|
|
accessControlConfig := &config.AccessControlConfig{}
|
|
accessControlConfig.Repositories = repositories
|
|
So(accessControlConfig.GetRepositories(), ShouldResemble, repositories)
|
|
})
|
|
|
|
Convey("Test GetAdminPolicy()", func() {
|
|
adminPolicy := config.Policy{
|
|
Actions: []string{"read", "write"},
|
|
Users: []string{"admin"},
|
|
}
|
|
accessControlConfig := &config.AccessControlConfig{}
|
|
accessControlConfig.AdminPolicy = adminPolicy
|
|
So(accessControlConfig.GetAdminPolicy(), ShouldResemble, adminPolicy)
|
|
})
|
|
|
|
Convey("Test GetMetrics()", func() {
|
|
metrics := config.Metrics{
|
|
Users: []string{"metrics-user"},
|
|
}
|
|
accessControlConfig := &config.AccessControlConfig{}
|
|
accessControlConfig.Metrics = metrics
|
|
So(accessControlConfig.GetMetrics(), ShouldResemble, metrics)
|
|
})
|
|
|
|
Convey("Test GetGroups()", func() {
|
|
groups := config.Groups{
|
|
"developers": config.Group{
|
|
Users: []string{"dev1", "dev2"},
|
|
},
|
|
}
|
|
accessControlConfig := &config.AccessControlConfig{}
|
|
accessControlConfig.Groups = groups
|
|
So(accessControlConfig.GetGroups(), ShouldResemble, groups)
|
|
})
|
|
})
|
|
|
|
Convey("Test Config getter methods", t, func() {
|
|
Convey("Test CopyAuthConfig()", func() {
|
|
Convey("Test with non-nil Auth", func() {
|
|
cfg := &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
Auth: &config.AuthConfig{
|
|
FailDelay: 5,
|
|
},
|
|
},
|
|
}
|
|
authConfig := cfg.CopyAuthConfig()
|
|
So(authConfig, ShouldNotBeNil)
|
|
So(authConfig.GetFailDelay(), ShouldEqual, 5)
|
|
})
|
|
|
|
Convey("Test with nil Auth", func() {
|
|
cfg := &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
Auth: nil,
|
|
},
|
|
}
|
|
authConfig := cfg.CopyAuthConfig()
|
|
So(authConfig, ShouldBeNil)
|
|
})
|
|
|
|
Convey("Test that returned AuthConfig is isolated from config mutations", func() {
|
|
// Create initial config with AuthConfig containing nested structures
|
|
cfg := &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
Auth: &config.AuthConfig{
|
|
FailDelay: 5,
|
|
HTPasswd: config.AuthHTPasswd{
|
|
Path: "/etc/htpasswd",
|
|
},
|
|
LDAP: &config.LDAPConfig{
|
|
Address: "ldap.example.com",
|
|
Port: 389,
|
|
},
|
|
Bearer: &config.BearerConfig{
|
|
Realm: "test-realm",
|
|
Service: "test-service",
|
|
Cert: "/path/to/cert",
|
|
},
|
|
OpenID: &config.OpenIDConfig{
|
|
Providers: map[string]config.OpenIDProviderConfig{
|
|
"google": {
|
|
Name: "Google",
|
|
ClientID: "google-client-id",
|
|
Scopes: []string{"openid", "email"},
|
|
},
|
|
},
|
|
},
|
|
APIKey: false,
|
|
SessionKeysFile: "/etc/session-keys",
|
|
SessionHashKey: []byte("hash-key"),
|
|
SessionEncryptKey: []byte("encrypt-key"),
|
|
SessionDriver: map[string]any{
|
|
"type": "redis",
|
|
"host": "localhost",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
// Get the AuthConfig reference
|
|
authConfig := cfg.CopyAuthConfig()
|
|
So(authConfig, ShouldNotBeNil)
|
|
So(authConfig.GetFailDelay(), ShouldEqual, 5)
|
|
So(authConfig.IsHtpasswdAuthEnabled(), ShouldBeTrue)
|
|
So(authConfig.IsLdapAuthEnabled(), ShouldBeTrue)
|
|
So(authConfig.IsBearerAuthEnabled(), ShouldBeTrue)
|
|
So(authConfig.IsOpenIDAuthEnabled(), ShouldBeTrue)
|
|
So(authConfig.IsAPIKeyEnabled(), ShouldBeFalse)
|
|
|
|
// Test deep copy isolation by modifying nested structures
|
|
authConfig.LDAP.Address = "modified-ldap.example.com"
|
|
authConfig.Bearer.Realm = "modified-realm"
|
|
authConfig.OpenID.Providers["google"].Scopes[0] = "modified-scope"
|
|
authConfig.SessionHashKey[0] = 'M'
|
|
authConfig.SessionDriver["type"] = "modified-driver"
|
|
|
|
// Verify original is unchanged
|
|
So(cfg.HTTP.Auth.LDAP.Address, ShouldEqual, "ldap.example.com")
|
|
So(cfg.HTTP.Auth.Bearer.Realm, ShouldEqual, "test-realm")
|
|
So(cfg.HTTP.Auth.OpenID.Providers["google"].Scopes[0], ShouldEqual, "openid")
|
|
So(cfg.HTTP.Auth.SessionHashKey[0], ShouldEqual, byte('h'))
|
|
So(cfg.HTTP.Auth.SessionDriver["type"], ShouldEqual, "redis")
|
|
})
|
|
|
|
Convey("Test that returned AuthConfig is isolated when config is updated via UpdateReloadableConfig", func() {
|
|
// Create initial config with AuthConfig
|
|
cfg := &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
Auth: &config.AuthConfig{
|
|
FailDelay: 5,
|
|
HTPasswd: config.AuthHTPasswd{
|
|
Path: "/etc/htpasswd",
|
|
},
|
|
APIKey: false,
|
|
},
|
|
},
|
|
}
|
|
|
|
// Get the AuthConfig reference
|
|
authConfig := cfg.CopyAuthConfig()
|
|
So(authConfig, ShouldNotBeNil)
|
|
So(authConfig.GetFailDelay(), ShouldEqual, 5)
|
|
So(authConfig.IsHtpasswdAuthEnabled(), ShouldBeTrue)
|
|
So(authConfig.IsAPIKeyEnabled(), ShouldBeFalse)
|
|
|
|
// Create new config with updated AuthConfig
|
|
// Note: UpdateReloadableConfig updates HTPasswd, LDAP, APIKey, and OpenID fields
|
|
newConfig := &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
Auth: &config.AuthConfig{
|
|
FailDelay: 15, // This field is NOT updated by UpdateReloadableConfig
|
|
HTPasswd: config.AuthHTPasswd{
|
|
Path: "/etc/updated-htpasswd", // This field IS updated by UpdateReloadableConfig
|
|
},
|
|
APIKey: true, // This field IS updated by UpdateReloadableConfig
|
|
},
|
|
},
|
|
}
|
|
|
|
// Update the config using UpdateReloadableConfig
|
|
cfg.UpdateReloadableConfig(newConfig)
|
|
|
|
// Verify that the returned AuthConfig is not affected by the update
|
|
// CopyAuthConfig() returns a copy, so the returned object should be isolated
|
|
So(authConfig.GetFailDelay(), ShouldEqual, 5) // Should remain unchanged
|
|
So(authConfig.IsHtpasswdAuthEnabled(), ShouldBeTrue) // Should remain unchanged (old path)
|
|
So(authConfig.IsAPIKeyEnabled(), ShouldBeFalse) // Should remain unchanged
|
|
|
|
// Verify that a new CopyAuthConfig() call returns the updated values
|
|
newAuthConfig := cfg.CopyAuthConfig()
|
|
So(newAuthConfig, ShouldNotBeNil)
|
|
// Should remain unchanged (not updated by UpdateReloadableConfig)
|
|
So(newAuthConfig.GetFailDelay(), ShouldEqual, 5)
|
|
So(newAuthConfig.IsHtpasswdAuthEnabled(), ShouldBeTrue) // Should be updated (new path)
|
|
// Should be updated by UpdateReloadableConfig
|
|
So(newAuthConfig.IsAPIKeyEnabled(), ShouldBeTrue)
|
|
})
|
|
|
|
Convey("Test that returned AuthConfig is isolated when config is set to nil", func() {
|
|
// Create initial config with AuthConfig
|
|
cfg := &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
Auth: &config.AuthConfig{
|
|
FailDelay: 5,
|
|
HTPasswd: config.AuthHTPasswd{
|
|
Path: "/etc/htpasswd",
|
|
},
|
|
APIKey: false,
|
|
},
|
|
},
|
|
}
|
|
|
|
// Get the AuthConfig reference
|
|
authConfig := cfg.CopyAuthConfig()
|
|
So(authConfig, ShouldNotBeNil)
|
|
So(authConfig.GetFailDelay(), ShouldEqual, 5)
|
|
So(authConfig.IsHtpasswdAuthEnabled(), ShouldBeTrue)
|
|
So(authConfig.IsAPIKeyEnabled(), ShouldBeFalse)
|
|
|
|
// Set the AuthConfig to nil
|
|
cfg.HTTP.Auth = nil
|
|
|
|
// Verify that the returned AuthConfig is not affected by setting to nil
|
|
So(authConfig, ShouldNotBeNil) // Should remain unchanged
|
|
So(authConfig.GetFailDelay(), ShouldEqual, 5) // Should remain unchanged
|
|
So(authConfig.IsHtpasswdAuthEnabled(), ShouldBeTrue) // Should remain unchanged
|
|
So(authConfig.IsAPIKeyEnabled(), ShouldBeFalse) // Should remain unchanged
|
|
|
|
// Verify that a new CopyAuthConfig() call returns nil
|
|
newAuthConfig := cfg.CopyAuthConfig()
|
|
So(newAuthConfig, ShouldBeNil) // Should be nil
|
|
})
|
|
})
|
|
|
|
Convey("Test CopyAccessControlConfig()", func() {
|
|
Convey("Test with non-nil AccessControl", func() {
|
|
testAccessControlConfig := &config.AccessControlConfig{
|
|
Repositories: config.Repositories{
|
|
"repo1": config.PolicyGroup{
|
|
Policies: []config.Policy{
|
|
{
|
|
Users: []string{"user1", "user2"},
|
|
Actions: []string{"read", "write"},
|
|
Groups: []string{"group1"},
|
|
},
|
|
},
|
|
DefaultPolicy: []string{"read"},
|
|
AnonymousPolicy: []string{"read"},
|
|
},
|
|
},
|
|
AdminPolicy: config.Policy{
|
|
Users: []string{"admin1"},
|
|
Actions: []string{"read", "write", "delete"},
|
|
Groups: []string{"admin-group"},
|
|
},
|
|
Groups: config.Groups{
|
|
"group1": config.Group{
|
|
Users: []string{"user1", "user2"},
|
|
},
|
|
},
|
|
Metrics: config.Metrics{
|
|
Users: []string{"metrics-user"},
|
|
},
|
|
}
|
|
cfg := &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
AccessControl: testAccessControlConfig,
|
|
},
|
|
}
|
|
accessControlConfig := cfg.CopyAccessControlConfig()
|
|
So(accessControlConfig, ShouldNotBeNil)
|
|
So(accessControlConfig.IsAuthzEnabled(), ShouldBeTrue)
|
|
|
|
// Test deep copy isolation
|
|
accessControlConfig.Repositories["repo1"].Policies[0].Users[0] = "modified-user"
|
|
accessControlConfig.Repositories["repo1"].DefaultPolicy[0] = "modified-policy"
|
|
accessControlConfig.AdminPolicy.Users[0] = "modified-admin"
|
|
accessControlConfig.Groups["group1"].Users[0] = "modified-group-user"
|
|
accessControlConfig.Metrics.Users[0] = "modified-metrics-user"
|
|
|
|
// Verify original is unchanged
|
|
So(cfg.HTTP.AccessControl.Repositories["repo1"].Policies[0].Users[0], ShouldEqual, "user1")
|
|
So(cfg.HTTP.AccessControl.Repositories["repo1"].DefaultPolicy[0], ShouldEqual, "read")
|
|
So(cfg.HTTP.AccessControl.AdminPolicy.Users[0], ShouldEqual, "admin1")
|
|
So(cfg.HTTP.AccessControl.Groups["group1"].Users[0], ShouldEqual, "user1")
|
|
So(cfg.HTTP.AccessControl.Metrics.Users[0], ShouldEqual, "metrics-user")
|
|
})
|
|
|
|
Convey("Test with nil AccessControl", func() {
|
|
cfg := &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
AccessControl: nil,
|
|
},
|
|
}
|
|
accessControlConfig := cfg.CopyAccessControlConfig()
|
|
So(accessControlConfig, ShouldBeNil)
|
|
})
|
|
})
|
|
|
|
Convey("Test CopyStorageConfig()", func() {
|
|
Convey("Test with non-nil Storage", func() {
|
|
cfg := &config.Config{
|
|
Storage: config.GlobalStorageConfig{
|
|
StorageConfig: config.StorageConfig{
|
|
RootDirectory: "/tmp/storage",
|
|
GC: true,
|
|
},
|
|
},
|
|
}
|
|
storageConfig := cfg.CopyStorageConfig()
|
|
So(storageConfig, ShouldNotBeNil)
|
|
So(storageConfig.RootDirectory, ShouldEqual, "/tmp/storage")
|
|
So(storageConfig.GC, ShouldBeTrue)
|
|
})
|
|
|
|
Convey("Test with nil Storage", func() {
|
|
cfg := &config.Config{
|
|
Storage: config.GlobalStorageConfig{},
|
|
}
|
|
storageConfig := cfg.CopyStorageConfig()
|
|
So(storageConfig, ShouldNotBeNil) // GlobalStorageConfig is a struct, not a pointer, so it's never nil
|
|
So(storageConfig.RootDirectory, ShouldEqual, "")
|
|
So(storageConfig.GC, ShouldBeFalse)
|
|
})
|
|
|
|
Convey("Test StorageConfig deep copy isolation", func() {
|
|
cfg := &config.Config{
|
|
Storage: config.GlobalStorageConfig{
|
|
StorageConfig: config.StorageConfig{
|
|
RootDirectory: "/tmp/storage",
|
|
GC: true,
|
|
Retention: config.ImageRetention{
|
|
DryRun: true,
|
|
Policies: []config.RetentionPolicy{
|
|
{
|
|
Repositories: []string{"repo1", "repo2"},
|
|
KeepTags: []config.KeepTagsPolicy{
|
|
{
|
|
Patterns: []string{"pattern1", "pattern2"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
StorageDriver: map[string]interface{}{
|
|
"type": "filesystem",
|
|
},
|
|
CacheDriver: map[string]interface{}{
|
|
"type": "redis",
|
|
},
|
|
},
|
|
SubPaths: map[string]config.StorageConfig{
|
|
"/subpath1": {
|
|
RootDirectory: "/tmp/subpath1",
|
|
Retention: config.ImageRetention{
|
|
Policies: []config.RetentionPolicy{
|
|
{
|
|
Repositories: []string{"subrepo1"},
|
|
},
|
|
},
|
|
},
|
|
StorageDriver: map[string]interface{}{
|
|
"type": "s3",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
// Get a copy of the storage config
|
|
storageConfig := cfg.CopyStorageConfig()
|
|
So(storageConfig, ShouldNotBeNil)
|
|
|
|
// Mutate the copy's fields
|
|
storageConfig.RootDirectory = "/modified/storage"
|
|
storageConfig.GC = false
|
|
storageConfig.Retention.Policies[0].Repositories[0] = "modified-repo"
|
|
storageConfig.Retention.Policies[0].KeepTags[0].Patterns[0] = "modified-pattern"
|
|
storageConfig.StorageDriver["type"] = "modified-driver"
|
|
storageConfig.CacheDriver["type"] = "modified-cache"
|
|
|
|
// Mutate SubPaths by getting a copy, modifying it, and putting it back
|
|
subPathConfig := storageConfig.SubPaths["/subpath1"]
|
|
subPathConfig.RootDirectory = "/modified/subpath1"
|
|
subPathConfig.Retention.Policies[0].Repositories[0] = "modified-subrepo"
|
|
subPathConfig.StorageDriver["type"] = "modified-s3"
|
|
storageConfig.SubPaths["/subpath1"] = subPathConfig
|
|
|
|
// Verify original config is unchanged
|
|
So(cfg.Storage.RootDirectory, ShouldEqual, "/tmp/storage")
|
|
So(cfg.Storage.GC, ShouldBeTrue)
|
|
So(cfg.Storage.Retention.Policies[0].Repositories[0], ShouldEqual, "repo1")
|
|
So(cfg.Storage.Retention.Policies[0].KeepTags[0].Patterns[0], ShouldEqual, "pattern1")
|
|
So(cfg.Storage.StorageDriver["type"], ShouldEqual, "filesystem")
|
|
So(cfg.Storage.CacheDriver["type"], ShouldEqual, "redis")
|
|
So(cfg.Storage.SubPaths["/subpath1"].RootDirectory, ShouldEqual, "/tmp/subpath1")
|
|
So(cfg.Storage.SubPaths["/subpath1"].Retention.Policies[0].Repositories[0], ShouldEqual, "subrepo1")
|
|
So(cfg.Storage.SubPaths["/subpath1"].StorageDriver["type"], ShouldEqual, "s3")
|
|
|
|
// Verify copy has the mutations
|
|
So(storageConfig.RootDirectory, ShouldEqual, "/modified/storage")
|
|
So(storageConfig.GC, ShouldBeFalse)
|
|
So(storageConfig.Retention.Policies[0].Repositories[0], ShouldEqual, "modified-repo")
|
|
So(storageConfig.Retention.Policies[0].KeepTags[0].Patterns[0], ShouldEqual, "modified-pattern")
|
|
So(storageConfig.StorageDriver["type"], ShouldEqual, "modified-driver")
|
|
So(storageConfig.CacheDriver["type"], ShouldEqual, "modified-cache")
|
|
So(storageConfig.SubPaths["/subpath1"].RootDirectory, ShouldEqual, "/modified/subpath1")
|
|
So(storageConfig.SubPaths["/subpath1"].Retention.Policies[0].Repositories[0], ShouldEqual, "modified-subrepo")
|
|
So(storageConfig.SubPaths["/subpath1"].StorageDriver["type"], ShouldEqual, "modified-s3")
|
|
})
|
|
})
|
|
|
|
Convey("Test CopyLogConfig()", func() {
|
|
Convey("Test with non-nil Log", func() {
|
|
cfg := &config.Config{
|
|
Log: &config.LogConfig{
|
|
Level: "info",
|
|
Output: "/tmp/logs",
|
|
},
|
|
}
|
|
logConfig := cfg.CopyLogConfig()
|
|
So(logConfig, ShouldNotBeNil)
|
|
So(logConfig.Level, ShouldEqual, "info")
|
|
So(logConfig.Output, ShouldEqual, "/tmp/logs")
|
|
})
|
|
|
|
Convey("Test with nil Log", func() {
|
|
cfg := &config.Config{
|
|
Log: nil,
|
|
}
|
|
logConfig := cfg.CopyLogConfig()
|
|
So(logConfig, ShouldBeNil)
|
|
})
|
|
})
|
|
|
|
Convey("Test CopyClusterConfig()", func() {
|
|
Convey("Test with non-nil Cluster", func() {
|
|
cfg := &config.Config{
|
|
Cluster: &config.ClusterConfig{
|
|
Members: []string{"node1", "node2"},
|
|
},
|
|
}
|
|
clusterConfig := cfg.CopyClusterConfig()
|
|
So(clusterConfig, ShouldNotBeNil)
|
|
So(len(clusterConfig.Members), ShouldEqual, 2)
|
|
})
|
|
|
|
Convey("Test with nil Cluster", func() {
|
|
cfg := &config.Config{
|
|
Cluster: nil,
|
|
}
|
|
clusterConfig := cfg.CopyClusterConfig()
|
|
So(clusterConfig, ShouldBeNil)
|
|
})
|
|
|
|
Convey("Test ClusterConfig deep copy isolation", func() {
|
|
cfg := &config.Config{
|
|
Cluster: &config.ClusterConfig{
|
|
Members: []string{"node1", "node2"},
|
|
HashKey: "test-key",
|
|
TLS: &config.TLSConfig{
|
|
Cert: "test-cert",
|
|
Key: "test-key",
|
|
CACert: "test-ca",
|
|
},
|
|
Proxy: &config.ClusterRequestProxyConfig{
|
|
LocalMemberClusterSocket: "127.0.0.1:8080",
|
|
LocalMemberClusterSocketIndex: 1,
|
|
},
|
|
},
|
|
}
|
|
|
|
// Get a copy of the cluster config
|
|
clusterConfig := cfg.CopyClusterConfig()
|
|
So(clusterConfig, ShouldNotBeNil)
|
|
|
|
// Mutate the copy
|
|
clusterConfig.Members[0] = "modified-node"
|
|
clusterConfig.HashKey = "modified-key"
|
|
clusterConfig.TLS.Cert = "modified-cert"
|
|
clusterConfig.Proxy.LocalMemberClusterSocket = "modified-socket"
|
|
|
|
// Verify original config is unchanged
|
|
So(cfg.Cluster.Members[0], ShouldEqual, "node1")
|
|
So(cfg.Cluster.HashKey, ShouldEqual, "test-key")
|
|
So(cfg.Cluster.TLS.Cert, ShouldEqual, "test-cert")
|
|
So(cfg.Cluster.Proxy.LocalMemberClusterSocket, ShouldEqual, "127.0.0.1:8080")
|
|
|
|
// Verify copy has the mutations
|
|
So(clusterConfig.Members[0], ShouldEqual, "modified-node")
|
|
So(clusterConfig.HashKey, ShouldEqual, "modified-key")
|
|
So(clusterConfig.TLS.Cert, ShouldEqual, "modified-cert")
|
|
So(clusterConfig.Proxy.LocalMemberClusterSocket, ShouldEqual, "modified-socket")
|
|
})
|
|
})
|
|
|
|
Convey("Test CopySchedulerConfig()", func() {
|
|
Convey("Test with non-nil Scheduler", func() {
|
|
cfg := &config.Config{
|
|
Scheduler: &config.SchedulerConfig{
|
|
NumWorkers: 4,
|
|
},
|
|
}
|
|
schedulerConfig := cfg.CopySchedulerConfig()
|
|
So(schedulerConfig, ShouldNotBeNil)
|
|
So(schedulerConfig.NumWorkers, ShouldEqual, 4)
|
|
})
|
|
|
|
Convey("Test with nil Scheduler", func() {
|
|
cfg := &config.Config{
|
|
Scheduler: nil,
|
|
}
|
|
schedulerConfig := cfg.CopySchedulerConfig()
|
|
So(schedulerConfig, ShouldBeNil)
|
|
})
|
|
})
|
|
|
|
Convey("Test GetVersionInfo()", func() {
|
|
Convey("Test with non-nil version info", func() {
|
|
cfg := &config.Config{
|
|
Commit: "abc123",
|
|
BinaryType: "server",
|
|
GoVersion: "go1.21",
|
|
DistSpecVersion: "1.1.1",
|
|
}
|
|
commit, binaryType, goVersion, distSpecVersion := cfg.GetVersionInfo()
|
|
So(commit, ShouldEqual, "abc123")
|
|
So(binaryType, ShouldEqual, "server")
|
|
So(goVersion, ShouldEqual, "go1.21")
|
|
So(distSpecVersion, ShouldEqual, "1.1.1")
|
|
})
|
|
|
|
Convey("Test with empty version info", func() {
|
|
cfg := &config.Config{
|
|
Commit: "",
|
|
BinaryType: "",
|
|
GoVersion: "",
|
|
DistSpecVersion: "",
|
|
}
|
|
commit, binaryType, goVersion, distSpecVersion := cfg.GetVersionInfo()
|
|
So(commit, ShouldEqual, "")
|
|
So(binaryType, ShouldEqual, "")
|
|
So(goVersion, ShouldEqual, "")
|
|
So(distSpecVersion, ShouldEqual, "")
|
|
})
|
|
})
|
|
|
|
Convey("Test GetRealm()", func() {
|
|
Convey("Test with non-empty Realm", func() {
|
|
cfg := &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
Realm: "my-realm",
|
|
},
|
|
}
|
|
realm := cfg.GetRealm()
|
|
So(realm, ShouldEqual, "my-realm")
|
|
})
|
|
|
|
Convey("Test with empty Realm", func() {
|
|
cfg := &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
Realm: "",
|
|
},
|
|
}
|
|
realm := cfg.GetRealm()
|
|
So(realm, ShouldEqual, "")
|
|
})
|
|
|
|
Convey("Test with nil Config", func() {
|
|
var cfg *config.Config = nil
|
|
realm := cfg.GetRealm()
|
|
So(realm, ShouldEqual, "")
|
|
})
|
|
})
|
|
|
|
Convey("Test CopyTLSConfig()", func() {
|
|
Convey("Test with non-empty TLS config", func() {
|
|
cfg := &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
TLS: &config.TLSConfig{
|
|
Cert: "/path/to/cert.pem",
|
|
Key: "/path/to/key.pem",
|
|
CACert: "/path/to/ca.pem",
|
|
},
|
|
},
|
|
}
|
|
tlsConfig := cfg.CopyTLSConfig()
|
|
So(tlsConfig, ShouldNotBeNil)
|
|
So(tlsConfig.Cert, ShouldEqual, "/path/to/cert.pem")
|
|
So(tlsConfig.Key, ShouldEqual, "/path/to/key.pem")
|
|
So(tlsConfig.CACert, ShouldEqual, "/path/to/ca.pem")
|
|
|
|
// Test copy isolation
|
|
tlsConfig.Cert = "/modified/cert.pem"
|
|
|
|
So(cfg.HTTP.TLS.Cert, ShouldEqual, "/path/to/cert.pem")
|
|
})
|
|
|
|
Convey("Test with nil TLS config", func() {
|
|
cfg := &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
TLS: nil,
|
|
},
|
|
}
|
|
tlsConfig := cfg.CopyTLSConfig()
|
|
So(tlsConfig, ShouldBeNil)
|
|
})
|
|
|
|
Convey("Test with nil Config", func() {
|
|
var cfg *config.Config = nil
|
|
tlsConfig := cfg.CopyTLSConfig()
|
|
So(tlsConfig, ShouldBeNil)
|
|
})
|
|
})
|
|
|
|
Convey("Test GetCompat()", func() {
|
|
Convey("Test with non-empty compat config", func() {
|
|
cfg := &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
Compat: []compat.MediaCompatibility{
|
|
"docker2s2",
|
|
"oci1",
|
|
},
|
|
},
|
|
}
|
|
compatConfig := cfg.GetCompat()
|
|
So(compatConfig, ShouldNotBeNil)
|
|
So(len(compatConfig), ShouldEqual, 2)
|
|
So(string(compatConfig[0]), ShouldEqual, "docker2s2")
|
|
So(string(compatConfig[1]), ShouldEqual, "oci1")
|
|
|
|
// Test copy isolation
|
|
compatConfig[0] = "modified-compat"
|
|
|
|
So(string(cfg.HTTP.Compat[0]), ShouldEqual, "docker2s2")
|
|
})
|
|
|
|
Convey("Test with nil compat config", func() {
|
|
cfg := &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
Compat: nil,
|
|
},
|
|
}
|
|
compatConfig := cfg.GetCompat()
|
|
So(compatConfig, ShouldBeNil)
|
|
})
|
|
|
|
Convey("Test with empty compat config", func() {
|
|
cfg := &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
Compat: []compat.MediaCompatibility{},
|
|
},
|
|
}
|
|
compatConfig := cfg.GetCompat()
|
|
So(compatConfig, ShouldNotBeNil)
|
|
So(len(compatConfig), ShouldEqual, 0)
|
|
})
|
|
|
|
Convey("Test with nil Config", func() {
|
|
var cfg *config.Config = nil
|
|
compatConfig := cfg.GetCompat()
|
|
So(compatConfig, ShouldBeNil)
|
|
})
|
|
})
|
|
|
|
Convey("Test GetHTTPAddress()", func() {
|
|
Convey("Test with non-empty address", func() {
|
|
cfg := &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
Address: "192.168.1.100",
|
|
},
|
|
}
|
|
address := cfg.GetHTTPAddress()
|
|
So(address, ShouldEqual, "192.168.1.100")
|
|
})
|
|
|
|
Convey("Test with empty address", func() {
|
|
cfg := &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
Address: "",
|
|
},
|
|
}
|
|
address := cfg.GetHTTPAddress()
|
|
So(address, ShouldEqual, "")
|
|
})
|
|
|
|
Convey("Test with nil Config", func() {
|
|
var cfg *config.Config = nil
|
|
address := cfg.GetHTTPAddress()
|
|
So(address, ShouldEqual, "")
|
|
})
|
|
})
|
|
|
|
Convey("Test GetHTTPPort()", func() {
|
|
Convey("Test with non-empty port", func() {
|
|
cfg := &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
Port: "8080",
|
|
},
|
|
}
|
|
port := cfg.GetHTTPPort()
|
|
So(port, ShouldEqual, "8080")
|
|
})
|
|
|
|
Convey("Test with empty port", func() {
|
|
cfg := &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
Port: "",
|
|
},
|
|
}
|
|
port := cfg.GetHTTPPort()
|
|
So(port, ShouldEqual, "")
|
|
})
|
|
|
|
Convey("Test with nil Config", func() {
|
|
var cfg *config.Config = nil
|
|
port := cfg.GetHTTPPort()
|
|
So(port, ShouldEqual, "")
|
|
})
|
|
})
|
|
|
|
Convey("Test GetAllowOrigin()", func() {
|
|
Convey("Test with non-empty allow origin", func() {
|
|
cfg := &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
AllowOrigin: "http://localhost:3000,https://example.com",
|
|
},
|
|
}
|
|
allowOrigin := cfg.GetAllowOrigin()
|
|
So(allowOrigin, ShouldEqual, "http://localhost:3000,https://example.com")
|
|
})
|
|
|
|
Convey("Test with empty allow origin", func() {
|
|
cfg := &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
AllowOrigin: "",
|
|
},
|
|
}
|
|
allowOrigin := cfg.GetAllowOrigin()
|
|
So(allowOrigin, ShouldEqual, "")
|
|
})
|
|
|
|
Convey("Test with nil Config", func() {
|
|
var cfg *config.Config = nil
|
|
allowOrigin := cfg.GetAllowOrigin()
|
|
So(allowOrigin, ShouldEqual, "")
|
|
})
|
|
})
|
|
|
|
Convey("Test CopyRatelimit()", func() {
|
|
Convey("Test with non-empty ratelimit config", func() {
|
|
rate := 100
|
|
cfg := &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
Ratelimit: &config.RatelimitConfig{
|
|
Rate: &rate,
|
|
Methods: []config.MethodRatelimitConfig{
|
|
{
|
|
Method: "GET",
|
|
Rate: 50,
|
|
},
|
|
{
|
|
Method: "POST",
|
|
Rate: 25,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
ratelimitConfig := cfg.CopyRatelimit()
|
|
So(ratelimitConfig, ShouldNotBeNil)
|
|
So(*ratelimitConfig.Rate, ShouldEqual, 100)
|
|
So(len(ratelimitConfig.Methods), ShouldEqual, 2)
|
|
So(ratelimitConfig.Methods[0].Method, ShouldEqual, "GET")
|
|
So(ratelimitConfig.Methods[0].Rate, ShouldEqual, 50)
|
|
So(ratelimitConfig.Methods[1].Method, ShouldEqual, "POST")
|
|
So(ratelimitConfig.Methods[1].Rate, ShouldEqual, 25)
|
|
|
|
// Test deep copy isolation
|
|
*ratelimitConfig.Rate = 200
|
|
ratelimitConfig.Methods[0].Rate = 75
|
|
ratelimitConfig.Methods[0].Method = "PUT"
|
|
|
|
So(*cfg.HTTP.Ratelimit.Rate, ShouldEqual, 100)
|
|
So(cfg.HTTP.Ratelimit.Methods[0].Rate, ShouldEqual, 50)
|
|
So(cfg.HTTP.Ratelimit.Methods[0].Method, ShouldEqual, "GET")
|
|
})
|
|
|
|
Convey("Test with nil ratelimit config", func() {
|
|
cfg := &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
Ratelimit: nil,
|
|
},
|
|
}
|
|
ratelimitConfig := cfg.CopyRatelimit()
|
|
So(ratelimitConfig, ShouldBeNil)
|
|
})
|
|
|
|
Convey("Test with nil Config", func() {
|
|
var cfg *config.Config = nil
|
|
ratelimitConfig := cfg.CopyRatelimit()
|
|
So(ratelimitConfig, ShouldBeNil)
|
|
})
|
|
})
|
|
})
|
|
|
|
Convey("Test Config utility methods", t, func() {
|
|
Convey("Test IsMTLSAuthEnabled()", func() {
|
|
// Test with nil Config
|
|
var cfg *config.Config = nil
|
|
|
|
So(cfg.IsMTLSAuthEnabled(), ShouldBeFalse)
|
|
|
|
// Test with Config but no TLS
|
|
cfg = &config.Config{}
|
|
So(cfg.IsMTLSAuthEnabled(), ShouldBeFalse)
|
|
|
|
// Test with Config and TLS but no client cert
|
|
cfg = &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
TLS: &config.TLSConfig{
|
|
Cert: "/path/to/cert.pem",
|
|
Key: "/path/to/key.pem",
|
|
},
|
|
},
|
|
}
|
|
So(cfg.IsMTLSAuthEnabled(), ShouldBeFalse)
|
|
|
|
// Test with Config and TLS with CA cert (mTLS)
|
|
cfg = &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
TLS: &config.TLSConfig{
|
|
Cert: "/path/to/cert.pem",
|
|
Key: "/path/to/key.pem",
|
|
CACert: "/path/to/ca-cert.pem",
|
|
},
|
|
},
|
|
}
|
|
So(cfg.IsMTLSAuthEnabled(), ShouldBeTrue)
|
|
|
|
// Test with HTPasswd enabled (should disable mTLS)
|
|
cfg = &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
Auth: &config.AuthConfig{
|
|
HTPasswd: config.AuthHTPasswd{
|
|
Path: "/path/to/htpasswd",
|
|
},
|
|
},
|
|
TLS: &config.TLSConfig{
|
|
Cert: "/path/to/cert.pem",
|
|
Key: "/path/to/key.pem",
|
|
CACert: "/path/to/ca-cert.pem",
|
|
},
|
|
},
|
|
}
|
|
So(cfg.IsMTLSAuthEnabled(), ShouldBeFalse) // Basic auth enabled, so mTLS disabled
|
|
|
|
// Test with LDAP enabled (should disable mTLS)
|
|
cfg = &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
Auth: &config.AuthConfig{
|
|
LDAP: &config.LDAPConfig{},
|
|
},
|
|
TLS: &config.TLSConfig{
|
|
Cert: "/path/to/cert.pem",
|
|
Key: "/path/to/key.pem",
|
|
CACert: "/path/to/ca-cert.pem",
|
|
},
|
|
},
|
|
}
|
|
So(cfg.IsMTLSAuthEnabled(), ShouldBeFalse) // Basic auth enabled, so mTLS disabled
|
|
|
|
// Test with API Key enabled (should disable mTLS)
|
|
cfg = &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
Auth: &config.AuthConfig{
|
|
APIKey: true,
|
|
},
|
|
TLS: &config.TLSConfig{
|
|
Cert: "/path/to/cert.pem",
|
|
Key: "/path/to/key.pem",
|
|
CACert: "/path/to/ca-cert.pem",
|
|
},
|
|
},
|
|
}
|
|
So(cfg.IsMTLSAuthEnabled(), ShouldBeFalse) // Basic auth enabled, so mTLS disabled
|
|
|
|
// Test with OpenID enabled (valid config - should disable mTLS)
|
|
cfg = &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
Auth: &config.AuthConfig{
|
|
OpenID: &config.OpenIDConfig{
|
|
Providers: map[string]config.OpenIDProviderConfig{
|
|
"google": {
|
|
ClientID: "client-id",
|
|
Issuer: "",
|
|
Scopes: []string{},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
TLS: &config.TLSConfig{
|
|
Cert: "/path/to/cert.pem",
|
|
Key: "/path/to/key.pem",
|
|
CACert: "/path/to/ca-cert.pem",
|
|
},
|
|
},
|
|
}
|
|
So(cfg.IsMTLSAuthEnabled(), ShouldBeFalse) // Basic auth enabled, so mTLS disabled
|
|
|
|
// Test with OpenID enabled (with Issuer - should disable mTLS)
|
|
cfg = &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
Auth: &config.AuthConfig{
|
|
OpenID: &config.OpenIDConfig{
|
|
Providers: map[string]config.OpenIDProviderConfig{
|
|
"google": {
|
|
ClientID: "",
|
|
Issuer: "https://accounts.google.com",
|
|
Scopes: []string{},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
TLS: &config.TLSConfig{
|
|
Cert: "/path/to/cert.pem",
|
|
Key: "/path/to/key.pem",
|
|
CACert: "/path/to/ca-cert.pem",
|
|
},
|
|
},
|
|
}
|
|
So(cfg.IsMTLSAuthEnabled(), ShouldBeFalse) // Basic auth enabled, so mTLS disabled
|
|
|
|
// Test with OpenID enabled (with Scopes - should disable mTLS)
|
|
cfg = &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
Auth: &config.AuthConfig{
|
|
OpenID: &config.OpenIDConfig{
|
|
Providers: map[string]config.OpenIDProviderConfig{
|
|
"google": {
|
|
ClientID: "",
|
|
Issuer: "",
|
|
Scopes: []string{"openid", "email"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
TLS: &config.TLSConfig{
|
|
Cert: "/path/to/cert.pem",
|
|
Key: "/path/to/key.pem",
|
|
CACert: "/path/to/ca-cert.pem",
|
|
},
|
|
},
|
|
}
|
|
So(cfg.IsMTLSAuthEnabled(), ShouldBeFalse) // Basic auth enabled, so mTLS disabled
|
|
|
|
// Test with OAuth2 provider (github) with ClientID (should disable mTLS)
|
|
cfg = &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
Auth: &config.AuthConfig{
|
|
OpenID: &config.OpenIDConfig{
|
|
Providers: map[string]config.OpenIDProviderConfig{
|
|
"github": {
|
|
ClientID: "github-client-id",
|
|
Scopes: []string{},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
TLS: &config.TLSConfig{
|
|
Cert: "/path/to/cert.pem",
|
|
Key: "/path/to/key.pem",
|
|
CACert: "/path/to/ca-cert.pem",
|
|
},
|
|
},
|
|
}
|
|
So(cfg.IsMTLSAuthEnabled(), ShouldBeFalse) // Basic auth enabled, so mTLS disabled
|
|
|
|
// Test with OAuth2 provider (github) with Scopes (should disable mTLS)
|
|
cfg = &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
Auth: &config.AuthConfig{
|
|
OpenID: &config.OpenIDConfig{
|
|
Providers: map[string]config.OpenIDProviderConfig{
|
|
"github": {
|
|
ClientID: "",
|
|
Scopes: []string{"user:email"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
TLS: &config.TLSConfig{
|
|
Cert: "/path/to/cert.pem",
|
|
Key: "/path/to/key.pem",
|
|
CACert: "/path/to/ca-cert.pem",
|
|
},
|
|
},
|
|
}
|
|
So(cfg.IsMTLSAuthEnabled(), ShouldBeFalse) // Basic auth enabled, so mTLS disabled
|
|
|
|
// Test with OpenID but empty config (should enable mTLS)
|
|
cfg = &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
Auth: &config.AuthConfig{
|
|
OpenID: &config.OpenIDConfig{
|
|
Providers: map[string]config.OpenIDProviderConfig{
|
|
"google": {
|
|
ClientID: "",
|
|
Issuer: "",
|
|
Scopes: []string{},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
TLS: &config.TLSConfig{
|
|
Cert: "/path/to/cert.pem",
|
|
Key: "/path/to/key.pem",
|
|
CACert: "/path/to/ca-cert.pem",
|
|
},
|
|
},
|
|
}
|
|
So(cfg.IsMTLSAuthEnabled(), ShouldBeTrue) // No basic auth, so mTLS enabled
|
|
|
|
// Test with OpenID but unsupported provider (should enable mTLS)
|
|
cfg = &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
Auth: &config.AuthConfig{
|
|
OpenID: &config.OpenIDConfig{
|
|
Providers: map[string]config.OpenIDProviderConfig{
|
|
"unsupported": {
|
|
ClientID: "client-id",
|
|
Scopes: []string{"scope"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
TLS: &config.TLSConfig{
|
|
Cert: "/path/to/cert.pem",
|
|
Key: "/path/to/key.pem",
|
|
CACert: "/path/to/ca-cert.pem",
|
|
},
|
|
},
|
|
}
|
|
So(cfg.IsMTLSAuthEnabled(), ShouldBeTrue) // No basic auth, so mTLS enabled
|
|
|
|
// Test with no authentication methods (should enable mTLS)
|
|
cfg = &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
Auth: &config.AuthConfig{},
|
|
TLS: &config.TLSConfig{
|
|
Cert: "/path/to/cert.pem",
|
|
Key: "/path/to/key.pem",
|
|
CACert: "/path/to/ca-cert.pem",
|
|
},
|
|
},
|
|
}
|
|
So(cfg.IsMTLSAuthEnabled(), ShouldBeTrue) // No basic auth, so mTLS enabled
|
|
|
|
// Test with nil Auth (should enable mTLS)
|
|
cfg = &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
Auth: nil,
|
|
TLS: &config.TLSConfig{
|
|
Cert: "/path/to/cert.pem",
|
|
Key: "/path/to/key.pem",
|
|
CACert: "/path/to/ca-cert.pem",
|
|
},
|
|
},
|
|
}
|
|
So(cfg.IsMTLSAuthEnabled(), ShouldBeTrue) // No basic auth, so mTLS enabled
|
|
})
|
|
|
|
Convey("Test IsCompatEnabled()", func() {
|
|
// Test with nil Config
|
|
var cfg *config.Config = nil
|
|
|
|
So(cfg.IsCompatEnabled(), ShouldBeFalse)
|
|
|
|
// Test with Config but no Compat
|
|
cfg = &config.Config{}
|
|
So(cfg.IsCompatEnabled(), ShouldBeFalse)
|
|
|
|
// Test with Config and Compat enabled
|
|
cfg = &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
Compat: []compat.MediaCompatibility{compat.DockerManifestV2SchemaV2},
|
|
},
|
|
}
|
|
So(cfg.IsCompatEnabled(), ShouldBeTrue)
|
|
})
|
|
|
|
Convey("Test IsOpenIDSupported()", func() {
|
|
// Test with unsupported provider
|
|
So(config.IsOpenIDSupported("unsupported"), ShouldBeFalse)
|
|
|
|
// Test with supported provider
|
|
So(config.IsOpenIDSupported("google"), ShouldBeTrue)
|
|
})
|
|
|
|
Convey("Test IsOauth2Supported()", func() {
|
|
// Test with unsupported provider
|
|
So(config.IsOauth2Supported("unsupported"), ShouldBeFalse)
|
|
|
|
// Test with supported provider
|
|
So(config.IsOauth2Supported("github"), ShouldBeTrue)
|
|
})
|
|
|
|
Convey("Test IsClustered() with nil ClusterConfig", func() {
|
|
var clusterConfig *config.ClusterConfig = nil
|
|
|
|
So(clusterConfig.IsClustered(), ShouldBeFalse)
|
|
})
|
|
|
|
Convey("Test IsClustered() with empty members", func() {
|
|
clusterConfig := &config.ClusterConfig{
|
|
Members: []string{},
|
|
}
|
|
So(clusterConfig.IsClustered(), ShouldBeFalse)
|
|
})
|
|
|
|
Convey("Test IsClustered() with single member", func() {
|
|
clusterConfig := &config.ClusterConfig{
|
|
Members: []string{"node1:8080"},
|
|
}
|
|
So(clusterConfig.IsClustered(), ShouldBeFalse)
|
|
})
|
|
|
|
Convey("Test IsClustered() with multiple members", func() {
|
|
clusterConfig := &config.ClusterConfig{
|
|
Members: []string{"node1:8080", "node2:8080"},
|
|
}
|
|
So(clusterConfig.IsClustered(), ShouldBeTrue)
|
|
})
|
|
})
|
|
|
|
Convey("Test CopyExtensionsConfig methods", t, func() {
|
|
Convey("Test IsSearchEnabled()", func() {
|
|
// Test with nil Config
|
|
var cfg *config.Config = nil
|
|
|
|
So(cfg.CopyExtensionsConfig().IsSearchEnabled(), ShouldBeFalse)
|
|
|
|
// Test with Config but nil Extensions
|
|
cfg = &config.Config{}
|
|
So(cfg.CopyExtensionsConfig().IsSearchEnabled(), ShouldBeFalse)
|
|
|
|
// Test with Config and Extensions but nil Search
|
|
cfg = &config.Config{
|
|
Extensions: &extconf.ExtensionConfig{},
|
|
}
|
|
So(cfg.CopyExtensionsConfig().IsSearchEnabled(), ShouldBeFalse)
|
|
|
|
// Test with Config and Extensions and Search but disabled
|
|
disabled := false
|
|
cfg = &config.Config{
|
|
Extensions: &extconf.ExtensionConfig{
|
|
Search: &extconf.SearchConfig{
|
|
BaseConfig: extconf.BaseConfig{
|
|
Enable: &disabled,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
So(cfg.CopyExtensionsConfig().IsSearchEnabled(), ShouldBeFalse)
|
|
|
|
// Test with Config and Extensions and Search enabled
|
|
enabled := true
|
|
cfg = &config.Config{
|
|
Extensions: &extconf.ExtensionConfig{
|
|
Search: &extconf.SearchConfig{
|
|
BaseConfig: extconf.BaseConfig{
|
|
Enable: &enabled,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
So(cfg.CopyExtensionsConfig().IsSearchEnabled(), ShouldBeTrue)
|
|
})
|
|
})
|
|
|
|
Convey("Test UpdateReloadableConfig()", t, func() {
|
|
Convey("Test with nil Config", func() {
|
|
var cfg *config.Config = nil
|
|
newConfig := &config.Config{}
|
|
|
|
So(func() { cfg.UpdateReloadableConfig(newConfig) }, ShouldNotPanic)
|
|
})
|
|
|
|
Convey("Test with nil newConfig.HTTP.Auth", func() {
|
|
// Create initial config with Auth
|
|
cfg := &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
Auth: &config.AuthConfig{
|
|
FailDelay: 5,
|
|
HTPasswd: config.AuthHTPasswd{
|
|
Path: "/etc/htpasswd",
|
|
},
|
|
APIKey: false,
|
|
},
|
|
},
|
|
}
|
|
|
|
// Create new config with nil Auth
|
|
newConfig := &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
Auth: nil, // This should not cause a panic
|
|
},
|
|
}
|
|
|
|
// This should not panic even though newConfig.HTTP.Auth is nil
|
|
So(func() { cfg.UpdateReloadableConfig(newConfig) }, ShouldNotPanic)
|
|
|
|
// Verify that the original Auth config remains unchanged
|
|
So(cfg.HTTP.Auth, ShouldNotBeNil)
|
|
So(cfg.HTTP.Auth.FailDelay, ShouldEqual, 5)
|
|
So(cfg.HTTP.Auth.HTPasswd.Path, ShouldEqual, "/etc/htpasswd")
|
|
So(cfg.HTTP.Auth.APIKey, ShouldBeFalse)
|
|
})
|
|
|
|
Convey("Test with AccessControl update", func() {
|
|
cfgAccessControl := &config.AccessControlConfig{}
|
|
cfgAccessControl.AdminPolicy = config.Policy{
|
|
Actions: []string{"read"},
|
|
}
|
|
cfg := &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
AccessControl: cfgAccessControl,
|
|
},
|
|
}
|
|
newConfigAccessControl := &config.AccessControlConfig{}
|
|
newConfigAccessControl.AdminPolicy = config.Policy{
|
|
Actions: []string{"read", "write"},
|
|
}
|
|
newConfig := &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
AccessControl: newConfigAccessControl,
|
|
},
|
|
}
|
|
cfg.UpdateReloadableConfig(newConfig)
|
|
So(cfg.CopyAccessControlConfig().GetAdminPolicy().Actions, ShouldResemble, []string{"read", "write"})
|
|
})
|
|
|
|
Convey("Test with Extensions update", func() {
|
|
// First set up a config with search enabled
|
|
enabled := true
|
|
cfg := &config.Config{
|
|
Extensions: &extconf.ExtensionConfig{
|
|
Search: &extconf.SearchConfig{
|
|
BaseConfig: extconf.BaseConfig{
|
|
Enable: &enabled,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
// Create new config with CVE config
|
|
newConfig := &config.Config{
|
|
Extensions: &extconf.ExtensionConfig{
|
|
Search: &extconf.SearchConfig{
|
|
BaseConfig: extconf.BaseConfig{
|
|
Enable: &enabled,
|
|
},
|
|
CVE: &extconf.CVEConfig{
|
|
UpdateInterval: time.Hour * 2,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
cfg.UpdateReloadableConfig(newConfig)
|
|
// The search should still be enabled and CVE config should be updated
|
|
So(cfg.CopyExtensionsConfig().IsSearchEnabled(), ShouldBeTrue)
|
|
})
|
|
|
|
Convey("Test search CVE config removal when new config has nil Search.CVE", func() {
|
|
// First set up a config with search enabled and CVE config
|
|
enabled := true
|
|
cfg := &config.Config{
|
|
Extensions: &extconf.ExtensionConfig{
|
|
Search: &extconf.SearchConfig{
|
|
BaseConfig: extconf.BaseConfig{
|
|
Enable: &enabled,
|
|
},
|
|
CVE: &extconf.CVEConfig{
|
|
UpdateInterval: time.Hour,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
So(cfg.CopyExtensionsConfig().IsSearchEnabled(), ShouldBeTrue)
|
|
So(cfg.Extensions.Search.CVE, ShouldNotBeNil)
|
|
|
|
// Create new config with Search but nil CVE
|
|
newConfig := &config.Config{
|
|
Extensions: &extconf.ExtensionConfig{
|
|
Search: &extconf.SearchConfig{
|
|
BaseConfig: extconf.BaseConfig{
|
|
Enable: &enabled,
|
|
},
|
|
CVE: nil, // This should trigger the removal
|
|
},
|
|
},
|
|
}
|
|
cfg.UpdateReloadableConfig(newConfig)
|
|
|
|
// Verify that the CVE config was removed
|
|
So(cfg.Extensions.Search.CVE, ShouldBeNil)
|
|
So(cfg.Extensions.Search.Enable, ShouldNotBeNil)
|
|
So(*cfg.Extensions.Search.Enable, ShouldBeTrue)
|
|
})
|
|
|
|
Convey("Test search CVE config removal when new config has nil Search", func() {
|
|
// First set up a config with search enabled and CVE config
|
|
enabled := true
|
|
cfg := &config.Config{
|
|
Extensions: &extconf.ExtensionConfig{
|
|
Search: &extconf.SearchConfig{
|
|
BaseConfig: extconf.BaseConfig{
|
|
Enable: &enabled,
|
|
},
|
|
CVE: &extconf.CVEConfig{
|
|
UpdateInterval: time.Hour,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
So(cfg.CopyExtensionsConfig().IsSearchEnabled(), ShouldBeTrue)
|
|
So(cfg.Extensions.Search.CVE, ShouldNotBeNil)
|
|
|
|
// Create new config with Extensions but nil Search
|
|
newConfig := &config.Config{
|
|
Extensions: &extconf.ExtensionConfig{
|
|
Search: nil, // This should trigger the removal
|
|
},
|
|
}
|
|
cfg.UpdateReloadableConfig(newConfig)
|
|
|
|
// Verify that the CVE config was removed
|
|
So(cfg.Extensions.Search.CVE, ShouldBeNil)
|
|
So(cfg.Extensions.Search.Enable, ShouldNotBeNil)
|
|
So(*cfg.Extensions.Search.Enable, ShouldBeTrue)
|
|
})
|
|
})
|
|
|
|
Convey("Test isOpenIDAuthProviderEnabled indirectly via IsMTLSAuthEnabled", t, func() {
|
|
Convey("Test with OpenID provider with empty config", func() {
|
|
cfg := &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
TLS: &config.TLSConfig{
|
|
Key: "key",
|
|
Cert: "cert",
|
|
CACert: "cacert",
|
|
},
|
|
Auth: &config.AuthConfig{
|
|
OpenID: &config.OpenIDConfig{
|
|
Providers: map[string]config.OpenIDProviderConfig{
|
|
"google": {
|
|
ClientID: "",
|
|
Issuer: "",
|
|
Scopes: []string{},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
AccessControl: &config.AccessControlConfig{},
|
|
},
|
|
}
|
|
// This should return true because isOpenIDAuthProviderEnabled returns false for empty config,
|
|
// so isBasicAuthnEnabled returns false, making IsMTLSAuthEnabled return true
|
|
So(cfg.IsMTLSAuthEnabled(), ShouldBeTrue)
|
|
})
|
|
|
|
Convey("Test with OpenID provider with valid config", func() {
|
|
cfg := &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
TLS: &config.TLSConfig{
|
|
Key: "key",
|
|
Cert: "cert",
|
|
CACert: "cacert",
|
|
},
|
|
Auth: &config.AuthConfig{
|
|
OpenID: &config.OpenIDConfig{
|
|
Providers: map[string]config.OpenIDProviderConfig{
|
|
"google": {
|
|
ClientID: "client-id",
|
|
Issuer: "",
|
|
Scopes: []string{},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
AccessControl: &config.AccessControlConfig{},
|
|
},
|
|
}
|
|
// This should return false because isOpenIDAuthProviderEnabled returns true for valid config,
|
|
// so isBasicAuthnEnabled returns true, making IsMTLSAuthEnabled return false
|
|
So(cfg.IsMTLSAuthEnabled(), ShouldBeFalse)
|
|
})
|
|
|
|
Convey("Test with unsupported OpenID provider", func() {
|
|
cfg := &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
TLS: &config.TLSConfig{
|
|
Key: "key",
|
|
Cert: "cert",
|
|
CACert: "cacert",
|
|
},
|
|
Auth: &config.AuthConfig{
|
|
OpenID: &config.OpenIDConfig{
|
|
Providers: map[string]config.OpenIDProviderConfig{
|
|
"unsupported": {
|
|
ClientID: "client-id",
|
|
Scopes: []string{"scope"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
AccessControl: &config.AccessControlConfig{},
|
|
},
|
|
}
|
|
// This should return true because isOpenIDAuthProviderEnabled returns false for unsupported provider,
|
|
// so isBasicAuthnEnabled returns false, making IsMTLSAuthEnabled return true
|
|
So(cfg.IsMTLSAuthEnabled(), ShouldBeTrue)
|
|
})
|
|
})
|
|
|
|
Convey("Test nil receiver coverage for all methods", t, func() {
|
|
Convey("Test AuthConfig methods with nil receiver", func() {
|
|
var authConfig *config.AuthConfig = nil
|
|
|
|
So(authConfig.IsLdapAuthEnabled(), ShouldBeFalse)
|
|
So(authConfig.IsHtpasswdAuthEnabled(), ShouldBeFalse)
|
|
So(authConfig.IsBearerAuthEnabled(), ShouldBeFalse)
|
|
So(authConfig.IsOpenIDAuthEnabled(), ShouldBeFalse)
|
|
So(authConfig.IsAPIKeyEnabled(), ShouldBeFalse)
|
|
So(authConfig.IsBasicAuthnEnabled(), ShouldBeFalse)
|
|
So(authConfig.GetFailDelay(), ShouldEqual, 0)
|
|
})
|
|
|
|
Convey("Test LDAPConfig methods with nil receiver", func() {
|
|
var ldapConfig *config.LDAPConfig = nil
|
|
|
|
So(ldapConfig.BindDN(), ShouldEqual, "")
|
|
So(ldapConfig.BindPassword(), ShouldEqual, "")
|
|
So(ldapConfig.SetBindDN("test"), ShouldBeNil)
|
|
So(ldapConfig.SetBindPassword("test"), ShouldBeNil)
|
|
})
|
|
|
|
Convey("Test AccessControlConfig methods with nil receiver", func() {
|
|
var accessControlConfig *config.AccessControlConfig = nil
|
|
|
|
So(accessControlConfig.IsAuthzEnabled(), ShouldBeFalse)
|
|
So(accessControlConfig.AnonymousPolicyExists(), ShouldBeFalse)
|
|
So(accessControlConfig.ContainsOnlyAnonymousPolicy(), ShouldBeTrue)
|
|
|
|
// Test getter methods
|
|
So(accessControlConfig.GetRepositories(), ShouldBeNil)
|
|
So(accessControlConfig.GetAdminPolicy(), ShouldResemble, config.Policy{})
|
|
So(accessControlConfig.GetMetrics(), ShouldResemble, config.Metrics{})
|
|
So(accessControlConfig.GetGroups(), ShouldBeNil)
|
|
})
|
|
|
|
Convey("Test Config methods with nil receiver", func() {
|
|
var cfg *config.Config = nil
|
|
|
|
// Test getter methods
|
|
So(cfg.CopyAuthConfig(), ShouldBeNil)
|
|
So(cfg.CopyAccessControlConfig(), ShouldBeNil)
|
|
So(cfg.GetHTTPAddress(), ShouldEqual, "")
|
|
So(cfg.GetHTTPPort(), ShouldEqual, "")
|
|
So(cfg.GetAllowOrigin(), ShouldEqual, "")
|
|
So(cfg.CopyTLSConfig(), ShouldBeNil)
|
|
So(cfg.CopyRatelimit(), ShouldBeNil)
|
|
So(cfg.GetCompat(), ShouldBeNil)
|
|
So(cfg.CopyStorageConfig(), ShouldResemble, config.GlobalStorageConfig{})
|
|
So(cfg.CopyExtensionsConfig(), ShouldBeNil)
|
|
So(cfg.CopyLogConfig(), ShouldBeNil)
|
|
So(cfg.CopyClusterConfig(), ShouldBeNil)
|
|
So(cfg.CopySchedulerConfig(), ShouldBeNil)
|
|
|
|
// Test GetVersionInfo
|
|
commit, binaryType, goVersion, distSpecVersion := cfg.GetVersionInfo()
|
|
So(commit, ShouldEqual, "")
|
|
So(binaryType, ShouldEqual, "")
|
|
So(goVersion, ShouldEqual, "")
|
|
So(distSpecVersion, ShouldEqual, "")
|
|
|
|
// Test boolean methods
|
|
So(cfg.IsMTLSAuthEnabled(), ShouldBeFalse)
|
|
So(cfg.IsRetentionEnabled(), ShouldBeFalse)
|
|
So(cfg.IsCompatEnabled(), ShouldBeFalse)
|
|
|
|
// Test Sanitize
|
|
So(cfg.Sanitize(), ShouldBeNil)
|
|
|
|
// Test UpdateReloadableConfig (should not panic)
|
|
newConfig := &config.Config{}
|
|
|
|
So(func() { cfg.UpdateReloadableConfig(newConfig) }, ShouldNotPanic)
|
|
})
|
|
})
|
|
|
|
Convey("Test AccessControlConfig copy isolation through CopyAccessControlConfig()", t, func() {
|
|
Convey("Test that mutations to retrieved AccessControlConfig copy do not affect original config", func() {
|
|
// Create a config with initial AccessControlConfig
|
|
cfg := &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
AccessControl: &config.AccessControlConfig{
|
|
AdminPolicy: config.Policy{
|
|
Actions: []string{"read"},
|
|
Users: []string{"admin"},
|
|
},
|
|
Repositories: config.Repositories{
|
|
"repo1": config.PolicyGroup{
|
|
DefaultPolicy: []string{"read"},
|
|
Policies: []config.Policy{
|
|
{
|
|
Actions: []string{"read"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
// Retrieve the AccessControlConfig (should be a copy)
|
|
accessControlConfig := cfg.CopyAccessControlConfig()
|
|
So(accessControlConfig, ShouldNotBeNil)
|
|
|
|
// Mutate the retrieved AccessControlConfig copy
|
|
accessControlConfig.AdminPolicy = config.Policy{
|
|
Actions: []string{"read", "write", "delete"},
|
|
Users: []string{"admin", "superadmin"},
|
|
}
|
|
|
|
// Add a new repository to the copy
|
|
newRepositories := config.Repositories{
|
|
"repo1": config.PolicyGroup{
|
|
DefaultPolicy: []string{"read"},
|
|
Policies: []config.Policy{
|
|
{
|
|
Actions: []string{"read"},
|
|
},
|
|
},
|
|
},
|
|
"repo2": config.PolicyGroup{
|
|
DefaultPolicy: []string{"read", "write"},
|
|
Policies: []config.Policy{
|
|
{
|
|
Actions: []string{"read", "write"},
|
|
Users: []string{"user1"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
accessControlConfig.Repositories = newRepositories
|
|
|
|
// Verify that the original config is unchanged
|
|
originalAccessControlConfig := cfg.CopyAccessControlConfig()
|
|
So(originalAccessControlConfig, ShouldNotBeNil)
|
|
|
|
// Check that admin policy remains unchanged in original
|
|
adminPolicy := originalAccessControlConfig.GetAdminPolicy()
|
|
So(adminPolicy.Actions, ShouldResemble, []string{"read"})
|
|
So(adminPolicy.Users, ShouldResemble, []string{"admin"})
|
|
|
|
// Check that repositories remain unchanged in original
|
|
repositories := originalAccessControlConfig.GetRepositories()
|
|
So(len(repositories), ShouldEqual, 1)
|
|
So(repositories["repo1"], ShouldNotBeNil)
|
|
So(repositories["repo1"].DefaultPolicy, ShouldResemble, []string{"read"})
|
|
})
|
|
|
|
Convey("Test that mutations to retrieved AccessControlConfig copy work with nil initial config", func() {
|
|
// Create a config with nil AccessControlConfig
|
|
cfg := &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
AccessControl: nil,
|
|
},
|
|
}
|
|
|
|
// Retrieve the AccessControlConfig (should return nil)
|
|
accessControlConfig := cfg.CopyAccessControlConfig()
|
|
So(accessControlConfig, ShouldBeNil)
|
|
|
|
// Create a new AccessControlConfig and set it
|
|
newAccessControlConfig := &config.AccessControlConfig{}
|
|
newAccessControlConfig.AdminPolicy = config.Policy{
|
|
Actions: []string{"read"},
|
|
Users: []string{"admin"},
|
|
}
|
|
|
|
// Manually set the AccessControlConfig on the original config
|
|
cfg.HTTP.AccessControl = newAccessControlConfig
|
|
|
|
// Now retrieve it again and verify it works
|
|
retrievedConfig := cfg.CopyAccessControlConfig()
|
|
So(retrievedConfig, ShouldNotBeNil)
|
|
|
|
// Mutate the retrieved config copy
|
|
retrievedConfig.AdminPolicy = config.Policy{
|
|
Actions: []string{"read", "write"},
|
|
Users: []string{"admin", "user"},
|
|
}
|
|
|
|
// Verify the original config is unchanged
|
|
finalConfig := cfg.CopyAccessControlConfig()
|
|
adminPolicy := finalConfig.GetAdminPolicy()
|
|
So(adminPolicy.Actions, ShouldResemble, []string{"read"})
|
|
So(adminPolicy.Users, ShouldResemble, []string{"admin"})
|
|
})
|
|
})
|
|
|
|
Convey("Test AccessControlConfig copy isolation through UpdateReloadableConfig()", t, func() {
|
|
Convey("Test that AccessControlConfig copies are isolated from UpdateReloadableConfig changes", func() {
|
|
// Create initial config with AccessControlConfig
|
|
cfg := &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
AccessControl: &config.AccessControlConfig{
|
|
AdminPolicy: config.Policy{
|
|
Actions: []string{"read"},
|
|
Users: []string{"admin"},
|
|
},
|
|
Repositories: config.Repositories{
|
|
"repo1": config.PolicyGroup{
|
|
DefaultPolicy: []string{"read"},
|
|
Policies: []config.Policy{
|
|
{
|
|
Actions: []string{"read"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
// Get initial reference to AccessControlConfig
|
|
initialAccessControlConfig := cfg.CopyAccessControlConfig()
|
|
So(initialAccessControlConfig, ShouldNotBeNil)
|
|
|
|
// Verify initial state
|
|
initialAdminPolicy := initialAccessControlConfig.GetAdminPolicy()
|
|
So(initialAdminPolicy.Actions, ShouldResemble, []string{"read"})
|
|
So(initialAdminPolicy.Users, ShouldResemble, []string{"admin"})
|
|
|
|
initialRepositories := initialAccessControlConfig.GetRepositories()
|
|
So(len(initialRepositories), ShouldEqual, 1)
|
|
So(initialRepositories["repo1"], ShouldNotBeNil)
|
|
|
|
// Create new config with updated AccessControlConfig
|
|
newConfig := &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
AccessControl: &config.AccessControlConfig{
|
|
AdminPolicy: config.Policy{
|
|
Actions: []string{"read", "write", "delete"},
|
|
Users: []string{"admin", "superadmin", "user"},
|
|
},
|
|
Repositories: config.Repositories{
|
|
"repo1": config.PolicyGroup{
|
|
DefaultPolicy: []string{"read", "write"},
|
|
Policies: []config.Policy{
|
|
{
|
|
Actions: []string{"read", "write"},
|
|
},
|
|
},
|
|
},
|
|
"repo2": config.PolicyGroup{
|
|
DefaultPolicy: []string{"read"},
|
|
Policies: []config.Policy{
|
|
{
|
|
Actions: []string{"read"},
|
|
Users: []string{"user1", "user2"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
// Update the config using UpdateReloadableConfig
|
|
cfg.UpdateReloadableConfig(newConfig)
|
|
|
|
// Verify that the old copy remains unchanged (copy isolation)
|
|
updatedAdminPolicy := initialAccessControlConfig.GetAdminPolicy()
|
|
So(updatedAdminPolicy.Actions, ShouldResemble, []string{"read"})
|
|
So(updatedAdminPolicy.Users, ShouldResemble, []string{"admin"})
|
|
|
|
updatedRepositories := initialAccessControlConfig.GetRepositories()
|
|
So(len(updatedRepositories), ShouldEqual, 1)
|
|
So(updatedRepositories["repo1"], ShouldNotBeNil)
|
|
So(updatedRepositories["repo1"].DefaultPolicy, ShouldResemble, []string{"read"})
|
|
|
|
// Verify that a new copy gets the updated data
|
|
newAccessControlConfig := cfg.CopyAccessControlConfig()
|
|
So(newAccessControlConfig, ShouldNotBeNil)
|
|
So(newAccessControlConfig, ShouldNotEqual, initialAccessControlConfig) // Different copy
|
|
|
|
newAdminPolicy := newAccessControlConfig.GetAdminPolicy()
|
|
So(newAdminPolicy.Actions, ShouldResemble, []string{"read", "write", "delete"})
|
|
So(newAdminPolicy.Users, ShouldResemble, []string{"admin", "superadmin", "user"})
|
|
})
|
|
|
|
Convey("Test that old AccessControlConfig reference works with nil initial config", func() {
|
|
// Create config with nil AccessControlConfig
|
|
cfg := &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
AccessControl: nil,
|
|
},
|
|
}
|
|
|
|
// Get initial reference (should be nil)
|
|
initialAccessControlConfig := cfg.CopyAccessControlConfig()
|
|
So(initialAccessControlConfig, ShouldBeNil)
|
|
|
|
// Create new config with AccessControlConfig
|
|
newConfig := &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
AccessControl: &config.AccessControlConfig{
|
|
AdminPolicy: config.Policy{
|
|
Actions: []string{"read", "write"},
|
|
Users: []string{"admin"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
// Update the config using UpdateReloadableConfig
|
|
cfg.UpdateReloadableConfig(newConfig)
|
|
|
|
// Verify that a new reference now gets the data
|
|
newAccessControlConfig := cfg.CopyAccessControlConfig()
|
|
So(newAccessControlConfig, ShouldNotBeNil)
|
|
|
|
adminPolicy := newAccessControlConfig.GetAdminPolicy()
|
|
So(adminPolicy.Actions, ShouldResemble, []string{"read", "write"})
|
|
So(adminPolicy.Users, ShouldResemble, []string{"admin"})
|
|
})
|
|
|
|
Convey("Test that old AccessControlConfig reference works when new config has nil AccessControlConfig", func() {
|
|
// Create initial config with AccessControlConfig
|
|
testAccessControlConfig := &config.AccessControlConfig{}
|
|
testAccessControlConfig.AdminPolicy = config.Policy{
|
|
Actions: []string{"read"},
|
|
Users: []string{"admin"},
|
|
}
|
|
cfg := &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
AccessControl: testAccessControlConfig,
|
|
},
|
|
}
|
|
|
|
// Get initial reference
|
|
initialAccessControlConfig := cfg.CopyAccessControlConfig()
|
|
So(initialAccessControlConfig, ShouldNotBeNil)
|
|
|
|
// Create new config with nil AccessControlConfig
|
|
newConfig := &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
AccessControl: nil,
|
|
},
|
|
}
|
|
|
|
// Update the config using UpdateReloadableConfig
|
|
cfg.UpdateReloadableConfig(newConfig)
|
|
|
|
// Verify that a new reference now returns nil
|
|
newAccessControlConfig := cfg.CopyAccessControlConfig()
|
|
So(newAccessControlConfig, ShouldBeNil)
|
|
})
|
|
})
|
|
|
|
Convey("Test ExtensionConfig copy isolation through CopyExtensionsConfig()", t, func() {
|
|
Convey("Test that mutations to retrieved ExtensionConfig copy do not affect original config", func() {
|
|
// Create a config with initial ExtensionConfig
|
|
enabled := true
|
|
cfg := &config.Config{
|
|
Extensions: &extconf.ExtensionConfig{
|
|
Search: &extconf.SearchConfig{
|
|
BaseConfig: extconf.BaseConfig{
|
|
Enable: &enabled,
|
|
},
|
|
CVE: &extconf.CVEConfig{
|
|
UpdateInterval: time.Hour,
|
|
Trivy: &extconf.TrivyConfig{
|
|
DBRepository: "original/trivy-db",
|
|
},
|
|
},
|
|
},
|
|
Sync: &syncconf.Config{
|
|
Enable: &enabled,
|
|
Registries: []syncconf.RegistryConfig{
|
|
{
|
|
URLs: []string{"http://original:5000"},
|
|
},
|
|
},
|
|
},
|
|
Metrics: &extconf.MetricsConfig{
|
|
BaseConfig: extconf.BaseConfig{
|
|
Enable: &enabled,
|
|
},
|
|
Prometheus: &extconf.PrometheusConfig{
|
|
Path: "/metrics",
|
|
},
|
|
},
|
|
Scrub: &extconf.ScrubConfig{
|
|
BaseConfig: extconf.BaseConfig{
|
|
Enable: &enabled,
|
|
},
|
|
Interval: 24 * time.Hour,
|
|
},
|
|
UI: &extconf.UIConfig{
|
|
BaseConfig: extconf.BaseConfig{
|
|
Enable: &enabled,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
// Retrieve the ExtensionConfig
|
|
extensionConfig := cfg.CopyExtensionsConfig()
|
|
So(extensionConfig, ShouldNotBeNil)
|
|
|
|
// Mutate the retrieved ExtensionConfig copy
|
|
disabled := false
|
|
extensionConfig.Search.Enable = &disabled
|
|
extensionConfig.Search.CVE.UpdateInterval = 2 * time.Hour
|
|
extensionConfig.Search.CVE.Trivy.DBRepository = "modified/trivy-db"
|
|
extensionConfig.Sync.Registries[0].URLs[0] = "http://modified:5000"
|
|
extensionConfig.Metrics.Prometheus.Path = "/custom/metrics"
|
|
extensionConfig.Scrub.Interval = 48 * time.Hour
|
|
extensionConfig.UI.Enable = &disabled
|
|
|
|
// Verify that the original config is unchanged
|
|
So(*cfg.Extensions.Search.Enable, ShouldBeTrue)
|
|
So(cfg.Extensions.Search.CVE.UpdateInterval, ShouldEqual, time.Hour)
|
|
So(cfg.Extensions.Search.CVE.Trivy.DBRepository, ShouldEqual, "original/trivy-db")
|
|
So(cfg.Extensions.Sync.Registries[0].URLs[0], ShouldEqual, "http://original:5000")
|
|
So(cfg.Extensions.Metrics.Prometheus.Path, ShouldEqual, "/metrics")
|
|
So(cfg.Extensions.Scrub.Interval, ShouldEqual, 24*time.Hour)
|
|
So(*cfg.Extensions.UI.Enable, ShouldBeTrue)
|
|
|
|
// Verify that the retrieved config has the mutations
|
|
So(*extensionConfig.Search.Enable, ShouldBeFalse)
|
|
So(extensionConfig.Search.CVE.UpdateInterval, ShouldEqual, 2*time.Hour)
|
|
So(extensionConfig.Search.CVE.Trivy.DBRepository, ShouldEqual, "modified/trivy-db")
|
|
So(extensionConfig.Sync.Registries[0].URLs[0], ShouldEqual, "http://modified:5000")
|
|
So(extensionConfig.Metrics.Prometheus.Path, ShouldEqual, "/custom/metrics")
|
|
So(extensionConfig.Scrub.Interval, ShouldEqual, 48*time.Hour)
|
|
So(*extensionConfig.UI.Enable, ShouldBeFalse)
|
|
})
|
|
|
|
Convey("Test that mutations to retrieved ExtensionConfig work with nil initial config", func() {
|
|
// Create a config with nil ExtensionConfig
|
|
cfg := &config.Config{
|
|
Extensions: nil,
|
|
}
|
|
|
|
// Retrieve the ExtensionConfig (should return nil)
|
|
extensionConfig := cfg.CopyExtensionsConfig()
|
|
So(extensionConfig, ShouldBeNil)
|
|
|
|
// Create a new ExtensionConfig and set it
|
|
enabled := true
|
|
newExtensionConfig := &extconf.ExtensionConfig{
|
|
Search: &extconf.SearchConfig{
|
|
BaseConfig: extconf.BaseConfig{
|
|
Enable: &enabled,
|
|
},
|
|
},
|
|
Metrics: &extconf.MetricsConfig{
|
|
BaseConfig: extconf.BaseConfig{
|
|
Enable: &enabled,
|
|
},
|
|
Prometheus: &extconf.PrometheusConfig{
|
|
Path: "/metrics",
|
|
},
|
|
},
|
|
}
|
|
|
|
// Manually set the ExtensionConfig on the original config
|
|
cfg.Extensions = newExtensionConfig
|
|
|
|
// Now retrieve it again and verify it works
|
|
retrievedConfig := cfg.CopyExtensionsConfig()
|
|
So(retrievedConfig, ShouldNotBeNil)
|
|
|
|
// Mutate the retrieved config
|
|
retrievedConfig.Metrics.Prometheus.Path = "/new/metrics"
|
|
|
|
// Verify the changes are NOT reflected in original config
|
|
finalConfig := cfg.CopyExtensionsConfig()
|
|
So(finalConfig.Metrics.Prometheus.Path, ShouldEqual, "/metrics")
|
|
})
|
|
})
|
|
|
|
Convey("Test ExtensionConfig copy isolation through UpdateReloadableConfig()", t, func() {
|
|
Convey("Test that ExtensionConfig copies are isolated from UpdateReloadableConfig changes", func() {
|
|
// Create initial config with ExtensionConfig
|
|
enabled := true
|
|
cfg := &config.Config{
|
|
Extensions: &extconf.ExtensionConfig{
|
|
Search: &extconf.SearchConfig{
|
|
BaseConfig: extconf.BaseConfig{
|
|
Enable: &enabled,
|
|
},
|
|
},
|
|
Metrics: &extconf.MetricsConfig{
|
|
BaseConfig: extconf.BaseConfig{
|
|
Enable: &enabled,
|
|
},
|
|
Prometheus: &extconf.PrometheusConfig{
|
|
Path: "/metrics",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
// Get initial reference to ExtensionConfig
|
|
initialExtensionConfig := cfg.CopyExtensionsConfig()
|
|
So(initialExtensionConfig, ShouldNotBeNil)
|
|
|
|
// Verify initial state
|
|
So(initialExtensionConfig.Metrics.Prometheus.Path, ShouldEqual, "/metrics")
|
|
So(initialExtensionConfig.Sync, ShouldBeNil)
|
|
So(initialExtensionConfig.Search.CVE, ShouldBeNil)
|
|
So(initialExtensionConfig.Scrub, ShouldBeNil)
|
|
|
|
// Create new config with updated ExtensionConfig
|
|
newConfig := &config.Config{
|
|
Extensions: &extconf.ExtensionConfig{
|
|
Search: &extconf.SearchConfig{
|
|
BaseConfig: extconf.BaseConfig{
|
|
Enable: &enabled,
|
|
},
|
|
CVE: &extconf.CVEConfig{
|
|
UpdateInterval: time.Hour * 2,
|
|
Trivy: &extconf.TrivyConfig{
|
|
DBRepository: "updated/trivy-db",
|
|
},
|
|
},
|
|
},
|
|
Metrics: &extconf.MetricsConfig{
|
|
BaseConfig: extconf.BaseConfig{
|
|
Enable: &enabled,
|
|
},
|
|
Prometheus: &extconf.PrometheusConfig{
|
|
Path: "/custom/metrics",
|
|
},
|
|
},
|
|
Sync: &syncconf.Config{
|
|
Enable: &enabled,
|
|
Registries: []syncconf.RegistryConfig{
|
|
{
|
|
URLs: []string{"http://registry1:5000", "http://registry2:5000"},
|
|
},
|
|
},
|
|
},
|
|
Scrub: &extconf.ScrubConfig{
|
|
BaseConfig: extconf.BaseConfig{
|
|
Enable: &enabled,
|
|
},
|
|
Interval: time.Hour * 12,
|
|
},
|
|
},
|
|
}
|
|
|
|
// Update the config using UpdateReloadableConfig
|
|
cfg.UpdateReloadableConfig(newConfig)
|
|
|
|
// Verify that the old reference remains unchanged (copy isolation)
|
|
So(initialExtensionConfig.Metrics.Prometheus.Path, ShouldEqual, "/metrics")
|
|
So(initialExtensionConfig.Sync, ShouldBeNil)
|
|
So(initialExtensionConfig.Search.CVE, ShouldBeNil)
|
|
So(initialExtensionConfig.Scrub, ShouldBeNil)
|
|
|
|
// Verify that a new reference gets the updated data
|
|
newExtensionConfig := cfg.CopyExtensionsConfig()
|
|
So(newExtensionConfig, ShouldNotBeNil)
|
|
So(newExtensionConfig, ShouldNotEqual, initialExtensionConfig) // Different references
|
|
|
|
So(newExtensionConfig.Metrics.Prometheus.Path, ShouldEqual, "/metrics")
|
|
So(newExtensionConfig.Sync, ShouldNotBeNil)
|
|
So(newExtensionConfig.Search.CVE, ShouldNotBeNil)
|
|
So(newExtensionConfig.Scrub, ShouldNotBeNil)
|
|
})
|
|
|
|
Convey("Test that old ExtensionConfig reference works with nil initial config", func() {
|
|
// Create config with nil ExtensionConfig
|
|
cfg := &config.Config{
|
|
Extensions: nil,
|
|
}
|
|
|
|
// Get initial reference (should be nil)
|
|
initialExtensionConfig := cfg.CopyExtensionsConfig()
|
|
So(initialExtensionConfig, ShouldBeNil)
|
|
|
|
// Create new config with ExtensionConfig
|
|
enabled := true
|
|
newConfig := &config.Config{
|
|
Extensions: &extconf.ExtensionConfig{
|
|
Search: &extconf.SearchConfig{
|
|
BaseConfig: extconf.BaseConfig{
|
|
Enable: &enabled,
|
|
},
|
|
},
|
|
Metrics: &extconf.MetricsConfig{
|
|
BaseConfig: extconf.BaseConfig{
|
|
Enable: &enabled,
|
|
},
|
|
Prometheus: &extconf.PrometheusConfig{
|
|
Path: "/new/metrics",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
// Update the config using UpdateReloadableConfig
|
|
cfg.UpdateReloadableConfig(newConfig)
|
|
|
|
// Verify that a new reference now gets the data
|
|
newExtensionConfig := cfg.CopyExtensionsConfig()
|
|
So(newExtensionConfig, ShouldNotBeNil)
|
|
|
|
// Note: UpdateReloadableConfig creates an empty ExtensionConfig when going from nil to non-nil,
|
|
// but doesn't copy the fields from newConfig.Extensions. It only updates specific parts.
|
|
// So the Search and Metrics fields will be nil in the new ExtensionConfig.
|
|
So(newExtensionConfig.Search, ShouldBeNil)
|
|
So(newExtensionConfig.Metrics, ShouldBeNil)
|
|
})
|
|
|
|
Convey("Test that old ExtensionConfig reference works when new config has nil ExtensionConfig", func() {
|
|
// Create initial config with ExtensionConfig
|
|
enabled := true
|
|
cfg := &config.Config{
|
|
Extensions: &extconf.ExtensionConfig{
|
|
Search: &extconf.SearchConfig{
|
|
BaseConfig: extconf.BaseConfig{
|
|
Enable: &enabled,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
// Get initial reference
|
|
initialExtensionConfig := cfg.CopyExtensionsConfig()
|
|
So(initialExtensionConfig, ShouldNotBeNil)
|
|
|
|
// Create new config with nil ExtensionConfig
|
|
newConfig := &config.Config{
|
|
Extensions: nil,
|
|
}
|
|
|
|
// Update the config using UpdateReloadableConfig
|
|
cfg.UpdateReloadableConfig(newConfig)
|
|
|
|
// Verify that the old reference remains unchanged (copy isolation)
|
|
So(initialExtensionConfig, ShouldNotBeNil)
|
|
So(initialExtensionConfig.Search, ShouldNotBeNil)
|
|
|
|
// Verify that a new reference now returns nil
|
|
newExtensionConfig := cfg.CopyExtensionsConfig()
|
|
So(newExtensionConfig, ShouldBeNil)
|
|
})
|
|
})
|
|
|
|
Convey("Test UpdateReloadableConfig LDAP config updates", t, func() {
|
|
Convey("Test LDAP config is updated in UpdateReloadableConfig", func() {
|
|
// Create initial config with LDAP
|
|
cfg := &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
Auth: &config.AuthConfig{
|
|
LDAP: &config.LDAPConfig{
|
|
Address: "ldap://old-server:389",
|
|
Port: 389,
|
|
Insecure: true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
// Create new config with updated LDAP
|
|
newConfig := &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
Auth: &config.AuthConfig{
|
|
LDAP: &config.LDAPConfig{
|
|
Address: "ldap://new-server:636",
|
|
Port: 636,
|
|
Insecure: false,
|
|
StartTLS: true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
// Update the config
|
|
cfg.UpdateReloadableConfig(newConfig)
|
|
|
|
// Verify LDAP config was updated
|
|
So(cfg.HTTP.Auth.LDAP, ShouldNotBeNil)
|
|
So(cfg.HTTP.Auth.LDAP.Address, ShouldEqual, "ldap://new-server:636")
|
|
So(cfg.HTTP.Auth.LDAP.Port, ShouldEqual, 636)
|
|
So(cfg.HTTP.Auth.LDAP.Insecure, ShouldBeFalse)
|
|
So(cfg.HTTP.Auth.LDAP.StartTLS, ShouldBeTrue)
|
|
})
|
|
|
|
Convey("Test LDAP config is set to nil when new config has nil LDAP", func() {
|
|
// Create initial config with LDAP
|
|
cfg := &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
Auth: &config.AuthConfig{
|
|
LDAP: &config.LDAPConfig{
|
|
Address: "ldap://old-server:389",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
// Create new config with nil LDAP
|
|
newConfig := &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
Auth: &config.AuthConfig{
|
|
LDAP: nil,
|
|
},
|
|
},
|
|
}
|
|
|
|
// Update the config
|
|
cfg.UpdateReloadableConfig(newConfig)
|
|
|
|
// Verify LDAP config was set to nil
|
|
So(cfg.HTTP.Auth.LDAP, ShouldBeNil)
|
|
})
|
|
|
|
Convey("Test LDAP config is created when going from nil to non-nil", func() {
|
|
// Create initial config with nil LDAP
|
|
cfg := &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
Auth: &config.AuthConfig{
|
|
LDAP: nil,
|
|
},
|
|
},
|
|
}
|
|
|
|
// Create new config with LDAP
|
|
newConfig := &config.Config{
|
|
HTTP: config.HTTPConfig{
|
|
Auth: &config.AuthConfig{
|
|
LDAP: &config.LDAPConfig{
|
|
Address: "ldap://new-server:389",
|
|
Port: 389,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
// Update the config
|
|
cfg.UpdateReloadableConfig(newConfig)
|
|
|
|
// Verify LDAP config was created
|
|
So(cfg.HTTP.Auth.LDAP, ShouldNotBeNil)
|
|
So(cfg.HTTP.Auth.LDAP.Address, ShouldEqual, "ldap://new-server:389")
|
|
So(cfg.HTTP.Auth.LDAP.Port, ShouldEqual, 389)
|
|
})
|
|
})
|
|
|
|
Convey("Test UpdateReloadableConfig Storage.SubPaths logic", t, func() {
|
|
Convey("Test existing SubPaths are updated", func() {
|
|
// Create initial config with SubPaths
|
|
cfg := &config.Config{
|
|
Storage: config.GlobalStorageConfig{
|
|
StorageConfig: config.StorageConfig{
|
|
GC: true,
|
|
Dedupe: false,
|
|
},
|
|
SubPaths: map[string]config.StorageConfig{
|
|
"/path1": {
|
|
GC: true,
|
|
Dedupe: false,
|
|
GCDelay: time.Hour,
|
|
GCInterval: time.Hour * 24,
|
|
},
|
|
"/path2": {
|
|
GC: false,
|
|
Dedupe: true,
|
|
GCDelay: time.Hour * 2,
|
|
GCInterval: time.Hour * 48,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
// Create new config with updated SubPaths
|
|
newConfig := &config.Config{
|
|
Storage: config.GlobalStorageConfig{
|
|
StorageConfig: config.StorageConfig{
|
|
GC: true,
|
|
Dedupe: false,
|
|
},
|
|
SubPaths: map[string]config.StorageConfig{
|
|
"/path1": {
|
|
GC: false, // Changed
|
|
Dedupe: true, // Changed
|
|
GCDelay: time.Hour * 2, // Changed
|
|
GCInterval: time.Hour * 12, // Changed
|
|
},
|
|
"/path2": {
|
|
GC: true, // Changed
|
|
Dedupe: false, // Changed
|
|
GCDelay: time.Hour * 3, // Changed
|
|
GCInterval: time.Hour * 36, // Changed
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
// Update the config
|
|
cfg.UpdateReloadableConfig(newConfig)
|
|
|
|
// Verify SubPaths were updated
|
|
So(len(cfg.Storage.SubPaths), ShouldEqual, 2)
|
|
|
|
// Check /path1
|
|
path1Config := cfg.Storage.SubPaths["/path1"]
|
|
So(path1Config.GC, ShouldBeFalse)
|
|
So(path1Config.Dedupe, ShouldBeTrue)
|
|
So(path1Config.GCDelay, ShouldEqual, time.Hour*2)
|
|
So(path1Config.GCInterval, ShouldEqual, time.Hour*12)
|
|
|
|
// Check /path2
|
|
path2Config := cfg.Storage.SubPaths["/path2"]
|
|
So(path2Config.GC, ShouldBeTrue)
|
|
So(path2Config.Dedupe, ShouldBeFalse)
|
|
So(path2Config.GCDelay, ShouldEqual, time.Hour*3)
|
|
So(path2Config.GCInterval, ShouldEqual, time.Hour*36)
|
|
})
|
|
|
|
Convey("Test new SubPaths are not added (only existing ones are updated)", func() {
|
|
// Create initial config with one SubPath
|
|
cfg := &config.Config{
|
|
Storage: config.GlobalStorageConfig{
|
|
StorageConfig: config.StorageConfig{
|
|
GC: true,
|
|
Dedupe: false,
|
|
},
|
|
SubPaths: map[string]config.StorageConfig{
|
|
"/path1": {
|
|
GC: true,
|
|
Dedupe: false,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
// Create new config with additional SubPath
|
|
newConfig := &config.Config{
|
|
Storage: config.GlobalStorageConfig{
|
|
StorageConfig: config.StorageConfig{
|
|
GC: true,
|
|
Dedupe: false,
|
|
},
|
|
SubPaths: map[string]config.StorageConfig{
|
|
"/path1": {
|
|
GC: false, // Update existing
|
|
Dedupe: true, // Update existing
|
|
},
|
|
"/path2": { // New path - should not be added
|
|
GC: true,
|
|
Dedupe: true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
// Update the config
|
|
cfg.UpdateReloadableConfig(newConfig)
|
|
|
|
// Verify only existing SubPath was updated, new one was not added
|
|
So(len(cfg.Storage.SubPaths), ShouldEqual, 1)
|
|
_, exists := cfg.Storage.SubPaths["/path2"]
|
|
So(exists, ShouldBeFalse) // New path not added
|
|
|
|
// Verify existing path was updated
|
|
path1Config := cfg.Storage.SubPaths["/path1"]
|
|
So(path1Config.GC, ShouldBeFalse)
|
|
So(path1Config.Dedupe, ShouldBeTrue)
|
|
})
|
|
|
|
Convey("Test SubPaths Retention is updated only when retention is enabled", func() {
|
|
// Create initial config with retention enabled and SubPaths
|
|
// Retention is enabled when there are policies with tag retention
|
|
cfg := &config.Config{
|
|
Storage: config.GlobalStorageConfig{
|
|
StorageConfig: config.StorageConfig{
|
|
GC: true,
|
|
Dedupe: false,
|
|
Retention: config.ImageRetention{
|
|
Policies: []config.RetentionPolicy{
|
|
{
|
|
Repositories: []string{"repo1"},
|
|
KeepTags: []config.KeepTagsPolicy{
|
|
{
|
|
MostRecentlyPulledCount: 10, // This enables retention
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
SubPaths: map[string]config.StorageConfig{
|
|
"/path1": {
|
|
GC: true,
|
|
Dedupe: false,
|
|
Retention: config.ImageRetention{
|
|
Policies: []config.RetentionPolicy{
|
|
{
|
|
Repositories: []string{"old-repo"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
// Create new config with updated SubPath retention
|
|
newConfig := &config.Config{
|
|
Storage: config.GlobalStorageConfig{
|
|
StorageConfig: config.StorageConfig{
|
|
GC: true,
|
|
Dedupe: false,
|
|
Retention: config.ImageRetention{
|
|
Policies: []config.RetentionPolicy{
|
|
{
|
|
Repositories: []string{"repo1"},
|
|
KeepTags: []config.KeepTagsPolicy{
|
|
{
|
|
MostRecentlyPulledCount: 10, // This enables retention
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
SubPaths: map[string]config.StorageConfig{
|
|
"/path1": {
|
|
GC: false,
|
|
Dedupe: true,
|
|
Retention: config.ImageRetention{
|
|
Policies: []config.RetentionPolicy{
|
|
{
|
|
Repositories: []string{"new-repo"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
// Update the config
|
|
cfg.UpdateReloadableConfig(newConfig)
|
|
|
|
// Verify SubPath was updated including Retention
|
|
path1Config := cfg.Storage.SubPaths["/path1"]
|
|
So(path1Config.GC, ShouldBeFalse)
|
|
So(path1Config.Dedupe, ShouldBeTrue)
|
|
So(len(path1Config.Retention.Policies), ShouldEqual, 1)
|
|
So(path1Config.Retention.Policies[0].Repositories[0], ShouldEqual, "new-repo")
|
|
})
|
|
|
|
Convey("Test SubPaths Retention is not updated when retention is disabled", func() {
|
|
// Create initial config with retention disabled and SubPaths
|
|
cfg := &config.Config{
|
|
Storage: config.GlobalStorageConfig{
|
|
StorageConfig: config.StorageConfig{
|
|
GC: true,
|
|
Dedupe: false,
|
|
// No Retention config - retention disabled
|
|
},
|
|
SubPaths: map[string]config.StorageConfig{
|
|
"/path1": {
|
|
GC: true,
|
|
Dedupe: false,
|
|
Retention: config.ImageRetention{
|
|
Policies: []config.RetentionPolicy{
|
|
{
|
|
Repositories: []string{"old-repo"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
// Create new config with updated SubPath retention
|
|
newConfig := &config.Config{
|
|
Storage: config.GlobalStorageConfig{
|
|
StorageConfig: config.StorageConfig{
|
|
GC: true,
|
|
Dedupe: false,
|
|
// No Retention config - retention disabled
|
|
},
|
|
SubPaths: map[string]config.StorageConfig{
|
|
"/path1": {
|
|
GC: false,
|
|
Dedupe: true,
|
|
Retention: config.ImageRetention{
|
|
Policies: []config.RetentionPolicy{
|
|
{
|
|
Repositories: []string{"new-repo"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
// Update the config
|
|
cfg.UpdateReloadableConfig(newConfig)
|
|
|
|
// Verify SubPath was updated but Retention was not
|
|
path1Config := cfg.Storage.SubPaths["/path1"]
|
|
So(path1Config.GC, ShouldBeFalse)
|
|
So(path1Config.Dedupe, ShouldBeTrue)
|
|
// Retention should remain unchanged (old value)
|
|
So(len(path1Config.Retention.Policies), ShouldEqual, 1)
|
|
So(path1Config.Retention.Policies[0].Repositories[0], ShouldEqual, "old-repo")
|
|
})
|
|
|
|
Convey("Test SubPaths with empty new config", func() {
|
|
// Create initial config with SubPaths
|
|
cfg := &config.Config{
|
|
Storage: config.GlobalStorageConfig{
|
|
StorageConfig: config.StorageConfig{
|
|
GC: true,
|
|
Dedupe: false,
|
|
},
|
|
SubPaths: map[string]config.StorageConfig{
|
|
"/path1": {
|
|
GC: true,
|
|
Dedupe: false,
|
|
},
|
|
"/path2": {
|
|
GC: false,
|
|
Dedupe: true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
// Create new config with empty SubPaths
|
|
newConfig := &config.Config{
|
|
Storage: config.GlobalStorageConfig{
|
|
StorageConfig: config.StorageConfig{
|
|
GC: true,
|
|
Dedupe: false,
|
|
},
|
|
SubPaths: map[string]config.StorageConfig{},
|
|
},
|
|
}
|
|
|
|
// Update the config
|
|
cfg.UpdateReloadableConfig(newConfig)
|
|
|
|
// Verify existing SubPaths remain unchanged (no updates applied)
|
|
So(len(cfg.Storage.SubPaths), ShouldEqual, 2)
|
|
path1Config := cfg.Storage.SubPaths["/path1"]
|
|
So(path1Config.GC, ShouldBeTrue) // Unchanged
|
|
So(path1Config.Dedupe, ShouldBeFalse) // Unchanged
|
|
path2Config := cfg.Storage.SubPaths["/path2"]
|
|
So(path2Config.GC, ShouldBeFalse) // Unchanged
|
|
So(path2Config.Dedupe, ShouldBeTrue) // Unchanged
|
|
})
|
|
})
|
|
}
|