mirror of
https://github.com/project-zot/zot.git
synced 2025-01-27 23:01:43 -05:00
feat(ldap): add option to load ldap from file (#1778)
Signed-off-by: Laurentiu Niculae <niculae.laurentiu1@gmail.com>
This commit is contained in:
parent
b2a9239c03
commit
272eb7cc43
10 changed files with 668 additions and 41 deletions
4
examples/config-ldap-credentials.json
Normal file
4
examples/config-ldap-credentials.json
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"bindDN":"cn=ldap-searcher,ou=Users,dc=example,dc=org",
|
||||
"bindPassword":"ldap-searcher-password"
|
||||
}
|
|
@ -13,14 +13,13 @@
|
|||
},
|
||||
"auth": {
|
||||
"ldap": {
|
||||
"credentialsFile": "examples/config-ldap-credentials.json",
|
||||
"address": "ldap.example.org",
|
||||
"port": 389,
|
||||
"startTLS": false,
|
||||
"baseDN":"ou=Users,dc=example,dc=org",
|
||||
"userAttribute": "uid",
|
||||
"userGroupAttribute": "memberOf",
|
||||
"bindDN":"cn=ldap-searcher,ou=Users,dc=example,dc=org",
|
||||
"bindPassword":"ldap-searcher-password",
|
||||
"skipVerify": true,
|
||||
"subtreeSearch": true
|
||||
},
|
||||
|
|
|
@ -266,9 +266,9 @@ func (amw *AuthnMiddleware) tryAuthnHandlers(ctlr *Controller) mux.MiddlewareFun
|
|||
UseSSL: !ldapConfig.Insecure,
|
||||
SkipTLS: !ldapConfig.StartTLS,
|
||||
Base: ldapConfig.BaseDN,
|
||||
BindDN: ldapConfig.BindDN,
|
||||
BindDN: ldapConfig.BindDN(),
|
||||
BindPassword: ldapConfig.BindPassword(),
|
||||
UserGroupAttribute: ldapConfig.UserGroupAttribute, // from config
|
||||
BindPassword: ldapConfig.BindPassword,
|
||||
UserFilter: fmt.Sprintf("(%s=%%s)", ldapConfig.UserAttribute),
|
||||
InsecureSkipVerify: ldapConfig.SkipVerify,
|
||||
ServerName: ldapConfig.Address,
|
||||
|
|
|
@ -121,21 +121,47 @@ type SchedulerConfig struct {
|
|||
NumWorkers int
|
||||
}
|
||||
|
||||
type LDAPCredentials struct {
|
||||
BindDN string
|
||||
BindPassword string
|
||||
}
|
||||
|
||||
type LDAPConfig struct {
|
||||
CredentialsFile string
|
||||
Port int
|
||||
Insecure bool
|
||||
StartTLS bool // if !Insecure, then StartTLS or LDAPs
|
||||
SkipVerify bool
|
||||
SubtreeSearch bool
|
||||
Address string
|
||||
BindDN string
|
||||
bindDN string `json:"-"`
|
||||
bindPassword string `json:"-"`
|
||||
UserGroupAttribute string
|
||||
BindPassword string
|
||||
BaseDN string
|
||||
UserAttribute string
|
||||
CACert string
|
||||
}
|
||||
|
||||
func (ldapConf *LDAPConfig) BindDN() string {
|
||||
return ldapConf.bindDN
|
||||
}
|
||||
|
||||
func (ldapConf *LDAPConfig) SetBindDN(bindDN string) *LDAPConfig {
|
||||
ldapConf.bindDN = bindDN
|
||||
|
||||
return ldapConf
|
||||
}
|
||||
|
||||
func (ldapConf *LDAPConfig) BindPassword() string {
|
||||
return ldapConf.bindPassword
|
||||
}
|
||||
|
||||
func (ldapConf *LDAPConfig) SetBindPassword(bindPassword string) *LDAPConfig {
|
||||
ldapConf.bindPassword = bindPassword
|
||||
|
||||
return ldapConf
|
||||
}
|
||||
|
||||
type LogConfig struct {
|
||||
Level string
|
||||
Output string
|
||||
|
@ -266,14 +292,14 @@ func (c *Config) Sanitize() *Config {
|
|||
panic(err)
|
||||
}
|
||||
|
||||
if c.HTTP.Auth != nil && c.HTTP.Auth.LDAP != nil && c.HTTP.Auth.LDAP.BindPassword != "" {
|
||||
if c.HTTP.Auth != nil && c.HTTP.Auth.LDAP != nil && c.HTTP.Auth.LDAP.bindPassword != "" {
|
||||
sanitizedConfig.HTTP.Auth.LDAP = &LDAPConfig{}
|
||||
|
||||
if err := DeepCopy(c.HTTP.Auth.LDAP, sanitizedConfig.HTTP.Auth.LDAP); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
sanitizedConfig.HTTP.Auth.LDAP.BindPassword = "******"
|
||||
sanitizedConfig.HTTP.Auth.LDAP.bindPassword = "******"
|
||||
}
|
||||
|
||||
return sanitizedConfig
|
||||
|
|
|
@ -69,11 +69,11 @@ func TestConfig(t *testing.T) {
|
|||
Convey("Test DeepCopy() & Sanitize()", t, func() {
|
||||
conf := config.New()
|
||||
So(conf, ShouldNotBeNil)
|
||||
authConfig := &config.AuthConfig{LDAP: &config.LDAPConfig{BindPassword: "oina"}}
|
||||
authConfig := &config.AuthConfig{LDAP: (&config.LDAPConfig{}).SetBindPassword("oina")}
|
||||
conf.HTTP.Auth = authConfig
|
||||
So(func() { conf.Sanitize() }, ShouldNotPanic)
|
||||
conf = conf.Sanitize()
|
||||
So(conf.HTTP.Auth.LDAP.BindPassword, ShouldEqual, "******")
|
||||
So(conf.HTTP.Auth.LDAP.BindPassword(), ShouldEqual, "******")
|
||||
|
||||
// negative
|
||||
obj := make(chan int)
|
||||
|
|
|
@ -53,6 +53,7 @@ import (
|
|||
"zotregistry.io/zot/pkg/api/config"
|
||||
"zotregistry.io/zot/pkg/api/constants"
|
||||
apiErr "zotregistry.io/zot/pkg/api/errors"
|
||||
"zotregistry.io/zot/pkg/cli/server"
|
||||
"zotregistry.io/zot/pkg/common"
|
||||
extconf "zotregistry.io/zot/pkg/extensions/config"
|
||||
"zotregistry.io/zot/pkg/log"
|
||||
|
@ -1985,8 +1986,13 @@ func (l *testLDAPServer) Stop() {
|
|||
}
|
||||
|
||||
func (l *testLDAPServer) Bind(bindDN, bindSimplePw string, conn net.Conn) (vldap.LDAPResultCode, error) {
|
||||
if bindDN == "" || bindSimplePw == "" {
|
||||
return vldap.LDAPResultInappropriateAuthentication, errors.ErrRequireCred
|
||||
if bindSimplePw == "" {
|
||||
switch bindDN {
|
||||
case "bad-user", "cn=fail-user-bind,ou=test":
|
||||
return vldap.LDAPResultInvalidCredentials, errors.ErrInvalidCred
|
||||
default:
|
||||
return vldap.LDAPResultSuccess, nil
|
||||
}
|
||||
}
|
||||
|
||||
if (bindDN == LDAPBindDN && bindSimplePw == LDAPBindPassword) ||
|
||||
|
@ -2000,7 +2006,25 @@ func (l *testLDAPServer) Bind(bindDN, bindSimplePw string, conn net.Conn) (vldap
|
|||
func (l *testLDAPServer) Search(boundDN string, req vldap.SearchRequest,
|
||||
conn net.Conn,
|
||||
) (vldap.ServerSearchResult, error) {
|
||||
if req.Filter == "(uid=fail-user-bind)" {
|
||||
return vldap.ServerSearchResult{
|
||||
Entries: []*vldap.Entry{
|
||||
{
|
||||
DN: fmt.Sprintf("cn=%s,%s", "fail-user-bind", LDAPBaseDN),
|
||||
Attributes: []*vldap.EntryAttribute{
|
||||
{
|
||||
Name: "memberOf",
|
||||
Values: []string{group},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ResultCode: vldap.LDAPResultSuccess,
|
||||
}, nil
|
||||
}
|
||||
|
||||
check := fmt.Sprintf("(uid=%s)", username)
|
||||
|
||||
if check == req.Filter {
|
||||
return vldap.ServerSearchResult{
|
||||
Entries: []*vldap.Entry{
|
||||
|
@ -2036,15 +2060,13 @@ func TestBasicAuthWithLDAP(t *testing.T) {
|
|||
conf := config.New()
|
||||
conf.HTTP.Port = port
|
||||
conf.HTTP.Auth = &config.AuthConfig{
|
||||
LDAP: &config.LDAPConfig{
|
||||
LDAP: (&config.LDAPConfig{
|
||||
Insecure: true,
|
||||
Address: LDAPAddress,
|
||||
Port: ldapPort,
|
||||
BindDN: LDAPBindDN,
|
||||
BindPassword: LDAPBindPassword,
|
||||
BaseDN: LDAPBaseDN,
|
||||
UserAttribute: "uid",
|
||||
},
|
||||
}).SetBindDN(LDAPBindDN).SetBindPassword(LDAPBindPassword),
|
||||
}
|
||||
ctlr := makeController(conf, t.TempDir())
|
||||
|
||||
|
@ -2077,6 +2099,293 @@ func TestBasicAuthWithLDAP(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestLDAPWithoutCreds(t *testing.T) {
|
||||
Convey("Make a new LDAP server", t, func() {
|
||||
l := newTestLDAPServer()
|
||||
port := test.GetFreePort()
|
||||
ldapPort, err := strconv.Atoi(port)
|
||||
So(err, ShouldBeNil)
|
||||
l.Start(ldapPort)
|
||||
defer l.Stop()
|
||||
|
||||
Convey("Server credentials succed ldap auth", func() {
|
||||
port = test.GetFreePort()
|
||||
baseURL := test.GetBaseURL(port)
|
||||
|
||||
conf := config.New()
|
||||
conf.HTTP.Port = port
|
||||
conf.HTTP.Auth = &config.AuthConfig{
|
||||
LDAP: (&config.LDAPConfig{
|
||||
Insecure: true,
|
||||
Address: LDAPAddress,
|
||||
Port: ldapPort,
|
||||
BaseDN: LDAPBaseDN,
|
||||
UserAttribute: "uid",
|
||||
}).SetBindDN("anonym"),
|
||||
}
|
||||
ctlr := makeController(conf, t.TempDir())
|
||||
|
||||
cm := test.NewControllerManager(ctlr)
|
||||
cm.StartAndWait(port)
|
||||
defer cm.StopServer()
|
||||
|
||||
// without creds, should get access error
|
||||
resp, err := resty.R().Get(baseURL + "/v2/")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
||||
var e apiErr.Error
|
||||
err = json.Unmarshal(resp.Body(), &e)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
resp, _ = resty.R().SetBasicAuth(username, "").Get(baseURL)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusNotFound)
|
||||
|
||||
resp, _ = resty.R().SetBasicAuth(username, "").Get(baseURL + "/v2/")
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
||||
|
||||
resp, _ = resty.R().SetBasicAuth(username, password).Get(baseURL + "/v2/")
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||
})
|
||||
|
||||
Convey("Server credentials fail ldap auth", func() {
|
||||
port = test.GetFreePort()
|
||||
baseURL := test.GetBaseURL(port)
|
||||
|
||||
conf := config.New()
|
||||
conf.HTTP.Port = port
|
||||
conf.HTTP.Auth = &config.AuthConfig{
|
||||
LDAP: (&config.LDAPConfig{
|
||||
Insecure: true,
|
||||
Address: LDAPAddress,
|
||||
Port: ldapPort,
|
||||
BaseDN: LDAPBaseDN,
|
||||
UserAttribute: "uid",
|
||||
}).SetBindDN("bad-user"),
|
||||
}
|
||||
ctlr := makeController(conf, t.TempDir())
|
||||
|
||||
cm := test.NewControllerManager(ctlr)
|
||||
cm.StartAndWait(port)
|
||||
defer cm.StopServer()
|
||||
|
||||
resp, _ := resty.R().SetBasicAuth(username, password).Get(baseURL + "/v2/")
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestBasicAuthWithLDAPFromFile(t *testing.T) {
|
||||
Convey("Make a new controller", t, func() {
|
||||
l := newTestLDAPServer()
|
||||
port := test.GetFreePort()
|
||||
ldapPort, err := strconv.Atoi(port)
|
||||
So(err, ShouldBeNil)
|
||||
l.Start(ldapPort)
|
||||
defer l.Stop()
|
||||
|
||||
port = test.GetFreePort()
|
||||
baseURL := test.GetBaseURL(port)
|
||||
tempDir := t.TempDir()
|
||||
|
||||
ldapConfigContent := fmt.Sprintf(`
|
||||
{
|
||||
"BindDN": "%v",
|
||||
"BindPassword": "%v"
|
||||
}`, LDAPBindDN, LDAPBindPassword)
|
||||
|
||||
ldapConfigPath := filepath.Join(tempDir, "ldap.json")
|
||||
|
||||
err = os.WriteFile(ldapConfigPath, []byte(ldapConfigContent), 0o600)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
configStr := fmt.Sprintf(`
|
||||
{
|
||||
"Storage": {
|
||||
"RootDirectory": "%s"
|
||||
},
|
||||
"HTTP": {
|
||||
"Address": "%s",
|
||||
"Port": "%s",
|
||||
"Auth": {
|
||||
"LDAP": {
|
||||
"CredentialsFile": "%s",
|
||||
"BaseDN": "%v",
|
||||
"UserAttribute": "uid",
|
||||
"UserGroupAttribute": "memberOf",
|
||||
"Insecure": true,
|
||||
"Address": "%v",
|
||||
"Port": %v
|
||||
}
|
||||
}
|
||||
}
|
||||
}`, tempDir, "127.0.0.1", port, ldapConfigPath, LDAPBaseDN, LDAPAddress, ldapPort)
|
||||
|
||||
configPath := filepath.Join(tempDir, "config.json")
|
||||
|
||||
err = os.WriteFile(configPath, []byte(configStr), 0o0600)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
server := server.NewServerRootCmd()
|
||||
server.SetArgs([]string{"serve", configPath})
|
||||
go func() {
|
||||
err := server.Execute()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
test.WaitTillServerReady(baseURL)
|
||||
|
||||
// without creds, should get access error
|
||||
resp, err := resty.R().Get(baseURL + "/v2/")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
||||
var e apiErr.Error
|
||||
err = json.Unmarshal(resp.Body(), &e)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// with creds, should get expected status code
|
||||
resp, _ = resty.R().SetBasicAuth(username, password).Get(baseURL)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusNotFound)
|
||||
|
||||
resp, _ = resty.R().SetBasicAuth(username, password).Get(baseURL + "/v2/")
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||
|
||||
// missing password
|
||||
resp, _ = resty.R().SetBasicAuth(username, "").Get(baseURL + "/v2/")
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
|
||||
})
|
||||
}
|
||||
|
||||
func TestLDAPConfigErrors(t *testing.T) {
|
||||
const configTemplate = `
|
||||
{
|
||||
"Storage": {
|
||||
"RootDirectory": "%s"
|
||||
},
|
||||
"HTTP": {
|
||||
"Address": "%s",
|
||||
"Port": "%s",
|
||||
"Auth": {
|
||||
"LDAP": {
|
||||
"CredentialsFile": "%s",
|
||||
"BaseDN": "%v",
|
||||
"UserAttribute": "%v",
|
||||
"UserGroupAttribute": "memberOf",
|
||||
"Insecure": true,
|
||||
"Address": "%v",
|
||||
"Port": %v
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
Convey("bad credentials file", t, func() {
|
||||
conf := config.New()
|
||||
tempDir := t.TempDir()
|
||||
ldapPort := 9000
|
||||
userAttribute := ""
|
||||
|
||||
ldapConfigContent := `bad-json`
|
||||
ldapConfigPath := filepath.Join(tempDir, "ldap.json")
|
||||
err := os.WriteFile(ldapConfigPath, []byte(ldapConfigContent), 0o600)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
configStr := fmt.Sprintf(configTemplate,
|
||||
tempDir, "127.0.0.1", "8000", ldapConfigPath, LDAPBaseDN, userAttribute, LDAPAddress, ldapPort)
|
||||
configPath := filepath.Join(tempDir, "config.json")
|
||||
err = os.WriteFile(configPath, []byte(configStr), 0o0600)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = server.LoadConfiguration(conf, configPath)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("UserAttribute is empty", t, func() {
|
||||
conf := config.New()
|
||||
tempDir := t.TempDir()
|
||||
ldapPort := 9000
|
||||
userAttribute := ""
|
||||
|
||||
ldapConfigContent := fmt.Sprintf(`
|
||||
{
|
||||
"BindDN": "%v",
|
||||
"BindPassword": "%v"
|
||||
}`, LDAPBindDN, LDAPBindPassword)
|
||||
ldapConfigPath := filepath.Join(tempDir, "ldap.json")
|
||||
err := os.WriteFile(ldapConfigPath, []byte(ldapConfigContent), 0o600)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
configStr := fmt.Sprintf(configTemplate,
|
||||
tempDir, "127.0.0.1", "8000", ldapConfigPath, LDAPBaseDN, userAttribute, LDAPAddress, ldapPort)
|
||||
configPath := filepath.Join(tempDir, "config.json")
|
||||
err = os.WriteFile(configPath, []byte(configStr), 0o0600)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = server.LoadConfiguration(conf, configPath)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("address is empty", t, func() {
|
||||
conf := config.New()
|
||||
tempDir := t.TempDir()
|
||||
ldapPort := 9000
|
||||
userAttribute := "uid"
|
||||
|
||||
ldapConfigContent := fmt.Sprintf(`
|
||||
{
|
||||
"BindDN": "%v",
|
||||
"BindPassword": "%v"
|
||||
}`, LDAPBindDN, LDAPBindPassword)
|
||||
ldapConfigPath := filepath.Join(tempDir, "ldap.json")
|
||||
err := os.WriteFile(ldapConfigPath, []byte(ldapConfigContent), 0o600)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
configStr := fmt.Sprintf(configTemplate,
|
||||
tempDir, "127.0.0.1", "8000", ldapConfigPath, LDAPBaseDN, userAttribute, "", ldapPort)
|
||||
configPath := filepath.Join(tempDir, "config.json")
|
||||
err = os.WriteFile(configPath, []byte(configStr), 0o0600)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = server.LoadConfiguration(conf, configPath)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("BaseDN is empty", t, func() {
|
||||
conf := config.New()
|
||||
tempDir := t.TempDir()
|
||||
ldapPort := 9000
|
||||
userAttribute := "uid"
|
||||
|
||||
ldapConfigContent := fmt.Sprintf(`
|
||||
{
|
||||
"BindDN": "%v",
|
||||
"BindPassword": "%v"
|
||||
}`, LDAPBindDN, LDAPBindPassword)
|
||||
ldapConfigPath := filepath.Join(tempDir, "ldap.json")
|
||||
err := os.WriteFile(ldapConfigPath, []byte(ldapConfigContent), 0o600)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
configStr := fmt.Sprintf(configTemplate,
|
||||
tempDir, "127.0.0.1", "8000", ldapConfigPath, "", userAttribute, LDAPAddress, ldapPort)
|
||||
configPath := filepath.Join(tempDir, "config.json")
|
||||
err = os.WriteFile(configPath, []byte(configStr), 0o0600)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = server.LoadConfiguration(conf, configPath)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGroupsPermissionsForLDAP(t *testing.T) {
|
||||
Convey("Make a new controller", t, func() {
|
||||
l := newTestLDAPServer()
|
||||
|
@ -2093,16 +2402,14 @@ func TestGroupsPermissionsForLDAP(t *testing.T) {
|
|||
conf := config.New()
|
||||
conf.HTTP.Port = port
|
||||
conf.HTTP.Auth = &config.AuthConfig{
|
||||
LDAP: &config.LDAPConfig{
|
||||
LDAP: (&config.LDAPConfig{
|
||||
Insecure: true,
|
||||
Address: LDAPAddress,
|
||||
Port: ldapPort,
|
||||
BindDN: LDAPBindDN,
|
||||
BindPassword: LDAPBindPassword,
|
||||
BaseDN: LDAPBaseDN,
|
||||
UserAttribute: "uid",
|
||||
UserGroupAttribute: "memberOf",
|
||||
},
|
||||
}).SetBindDN(LDAPBindDN).SetBindPassword(LDAPBindPassword),
|
||||
}
|
||||
|
||||
repoName, seed := test.GenerateRandomName()
|
||||
|
@ -2145,6 +2452,101 @@ func TestGroupsPermissionsForLDAP(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestLDAPConfigFromFile(t *testing.T) {
|
||||
Convey("Make a new controller", t, func() {
|
||||
l := newTestLDAPServer()
|
||||
port := test.GetFreePort()
|
||||
ldapPort, err := strconv.Atoi(port)
|
||||
So(err, ShouldBeNil)
|
||||
l.Start(ldapPort)
|
||||
defer l.Stop()
|
||||
|
||||
port = test.GetFreePort()
|
||||
baseURL := test.GetBaseURL(port)
|
||||
tempDir := t.TempDir()
|
||||
|
||||
ldapConfigContent := fmt.Sprintf(`
|
||||
{
|
||||
"bindDN": "%v",
|
||||
"bindPassword": "%v"
|
||||
}`, LDAPBindDN, LDAPBindPassword)
|
||||
|
||||
ldapConfigPath := filepath.Join(tempDir, "ldap.json")
|
||||
|
||||
err = os.WriteFile(ldapConfigPath, []byte(ldapConfigContent), 0o600)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
configStr := fmt.Sprintf(`
|
||||
{
|
||||
"Storage": {
|
||||
"RootDirectory": "%s"
|
||||
},
|
||||
"HTTP": {
|
||||
"Address": "%s",
|
||||
"Port": "%s",
|
||||
"Auth": {
|
||||
"LDAP": {
|
||||
"CredentialsFile": "%s",
|
||||
"BaseDN": "%v",
|
||||
"UserAttribute": "uid",
|
||||
"UserGroupAttribute": "memberOf",
|
||||
"Insecure": true,
|
||||
"Address": "%v",
|
||||
"Port": %v
|
||||
}
|
||||
},
|
||||
"AccessControl": {
|
||||
"repositories": {
|
||||
"test-ldap": {
|
||||
"Policies": [
|
||||
{
|
||||
"Users": null,
|
||||
"Actions": [
|
||||
"read",
|
||||
"create"
|
||||
],
|
||||
"Groups": [
|
||||
"test"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"Groups": {
|
||||
"test": {
|
||||
"Users": [
|
||||
"test"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`, tempDir, "127.0.0.1", port, ldapConfigPath, LDAPBaseDN, LDAPAddress, ldapPort)
|
||||
|
||||
configPath := filepath.Join(tempDir, "config.json")
|
||||
|
||||
err = os.WriteFile(configPath, []byte(configStr), 0o0600)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
server := server.NewServerRootCmd()
|
||||
server.SetArgs([]string{"serve", configPath})
|
||||
go func() {
|
||||
err := server.Execute()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
test.WaitTillServerReady(baseURL)
|
||||
|
||||
repo := "test-ldap"
|
||||
img := CreateDefaultImage()
|
||||
|
||||
err = UploadImageWithBasicAuth(img, baseURL, repo, img.DigestStr(), username, password)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
}
|
||||
|
||||
func TestLDAPFailures(t *testing.T) {
|
||||
Convey("Make a LDAP conn", t, func() {
|
||||
l := newTestLDAPServer()
|
||||
|
@ -2181,6 +2583,69 @@ func TestLDAPFailures(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestLDAPClient(t *testing.T) {
|
||||
Convey("LDAP Client", t, func() {
|
||||
l := newTestLDAPServer()
|
||||
port := test.GetFreePort()
|
||||
ldapPort, err := strconv.Atoi(port)
|
||||
So(err, ShouldBeNil)
|
||||
l.Start(ldapPort)
|
||||
defer l.Stop()
|
||||
|
||||
// bad server credentials
|
||||
lClient := &api.LDAPClient{
|
||||
Host: LDAPAddress,
|
||||
Port: ldapPort,
|
||||
BindDN: "bad-user",
|
||||
BindPassword: "bad-pass",
|
||||
SkipTLS: true,
|
||||
}
|
||||
|
||||
_, _, _, err = lClient.Authenticate("bad-user", "bad-pass")
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
// bad credentials with anonymous authentication
|
||||
lClient = &api.LDAPClient{
|
||||
Host: LDAPAddress,
|
||||
Port: ldapPort,
|
||||
BindDN: "bad-user",
|
||||
BindPassword: "",
|
||||
SkipTLS: true,
|
||||
}
|
||||
|
||||
_, _, _, err = lClient.Authenticate("user", "")
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
// bad user credentials with anonymous authentication
|
||||
lClient = &api.LDAPClient{
|
||||
Host: LDAPAddress,
|
||||
Port: ldapPort,
|
||||
BindDN: LDAPBindDN,
|
||||
BindPassword: LDAPBindPassword,
|
||||
Base: LDAPBaseDN,
|
||||
UserFilter: "(uid=%s)",
|
||||
SkipTLS: true,
|
||||
}
|
||||
|
||||
_, _, _, err = lClient.Authenticate("fail-user-bind", "")
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
// bad user credentials with anonymous authentication
|
||||
lClient = &api.LDAPClient{
|
||||
Host: LDAPAddress,
|
||||
Port: ldapPort,
|
||||
BindDN: LDAPBindDN,
|
||||
BindPassword: LDAPBindPassword,
|
||||
Base: LDAPBaseDN,
|
||||
UserFilter: "(uid=%s)",
|
||||
SkipTLS: true,
|
||||
}
|
||||
|
||||
_, _, _, err = lClient.Authenticate("fail-user-bind", "pass")
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
}
|
||||
|
||||
func TestBearerAuth(t *testing.T) {
|
||||
Convey("Make a new controller", t, func() {
|
||||
authTestServer := authutils.MakeAuthTestServer(ServerKey, UnauthorizedNamespace)
|
||||
|
@ -2681,15 +3146,13 @@ func TestOpenIDMiddleware(t *testing.T) {
|
|||
HTPasswd: config.AuthHTPasswd{
|
||||
Path: htpasswdPath,
|
||||
},
|
||||
LDAP: &config.LDAPConfig{
|
||||
LDAP: (&config.LDAPConfig{
|
||||
Insecure: true,
|
||||
Address: LDAPAddress,
|
||||
Port: ldapPort,
|
||||
BindDN: LDAPBindDN,
|
||||
BindPassword: LDAPBindPassword,
|
||||
BaseDN: LDAPBaseDN,
|
||||
UserAttribute: "uid",
|
||||
},
|
||||
}).SetBindDN(LDAPBindDN).SetBindPassword(LDAPBindPassword),
|
||||
OpenID: &config.OpenIDConfig{
|
||||
Providers: map[string]config.OpenIDProviderConfig{
|
||||
"oidc": {
|
||||
|
@ -3162,15 +3625,13 @@ func TestAuthnSessionErrors(t *testing.T) {
|
|||
HTPasswd: config.AuthHTPasswd{
|
||||
Path: htpasswdPath,
|
||||
},
|
||||
LDAP: &config.LDAPConfig{
|
||||
LDAP: (&config.LDAPConfig{
|
||||
Insecure: true,
|
||||
Address: LDAPAddress,
|
||||
Port: ldapPort,
|
||||
BindDN: LDAPBindDN,
|
||||
BindPassword: LDAPBindPassword,
|
||||
BaseDN: LDAPBaseDN,
|
||||
UserAttribute: "uid",
|
||||
},
|
||||
}).SetBindDN(LDAPBindDN).SetBindPassword(LDAPBindPassword),
|
||||
OpenID: &config.OpenIDConfig{
|
||||
Providers: map[string]config.OpenIDProviderConfig{
|
||||
"oidc": {
|
||||
|
|
|
@ -140,7 +140,7 @@ func (lc *LDAPClient) Authenticate(username, password string) (bool, map[string]
|
|||
}
|
||||
|
||||
// First bind with a read only user
|
||||
if lc.BindDN != "" && lc.BindPassword != "" {
|
||||
if lc.BindPassword != "" {
|
||||
err := lc.Conn.Bind(lc.BindDN, lc.BindPassword)
|
||||
if err != nil {
|
||||
lc.Log.Error().Err(err).Str("bindDN", lc.BindDN).Msg("bind failed")
|
||||
|
@ -148,6 +148,16 @@ func (lc *LDAPClient) Authenticate(username, password string) (bool, map[string]
|
|||
lc.Conn.Close()
|
||||
lc.Conn = nil
|
||||
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
err := lc.Conn.UnauthenticatedBind(lc.BindDN)
|
||||
if err != nil {
|
||||
lc.Log.Error().Err(err).Str("bindDN", lc.BindDN).Msg("bind failed")
|
||||
// clean up the cached conn, so we can retry
|
||||
lc.Conn.Close()
|
||||
lc.Conn = nil
|
||||
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
|
|
@ -738,6 +738,10 @@ func LoadConfiguration(config *config.Config, configPath string) error {
|
|||
return zerr.ErrBadConfig
|
||||
}
|
||||
|
||||
if err := updateLDAPConfig(config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// defaults
|
||||
applyDefaultValues(config, viperInstance, log)
|
||||
|
||||
|
@ -752,6 +756,50 @@ func LoadConfiguration(config *config.Config, configPath string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func updateLDAPConfig(conf *config.Config) error {
|
||||
if conf.HTTP.Auth == nil || conf.HTTP.Auth.LDAP == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if conf.HTTP.Auth.LDAP.CredentialsFile == "" {
|
||||
conf.HTTP.Auth.LDAP.SetBindDN("anonym-user")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
newLDAPCredentials, err := readLDAPCredentials(conf.HTTP.Auth.LDAP.CredentialsFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
conf.HTTP.Auth.LDAP.SetBindDN(newLDAPCredentials.BindDN)
|
||||
conf.HTTP.Auth.LDAP.SetBindPassword(newLDAPCredentials.BindPassword)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func readLDAPCredentials(ldapConfigPath string) (config.LDAPCredentials, error) {
|
||||
viperInstance := viper.NewWithOptions(viper.KeyDelimiter("::"))
|
||||
|
||||
viperInstance.SetConfigFile(ldapConfigPath)
|
||||
|
||||
if err := viperInstance.ReadInConfig(); err != nil {
|
||||
log.Error().Err(err).Msg("error while reading configuration")
|
||||
|
||||
return config.LDAPCredentials{}, err
|
||||
}
|
||||
|
||||
var ldapCredentials config.LDAPCredentials
|
||||
|
||||
if err := viperInstance.Unmarshal(&ldapCredentials); err != nil {
|
||||
log.Error().Err(err).Msg("error while unmarshaling new config")
|
||||
|
||||
return config.LDAPCredentials{}, err
|
||||
}
|
||||
|
||||
return ldapCredentials, nil
|
||||
}
|
||||
|
||||
func authzContainsOnlyAnonymousPolicy(cfg *config.Config) bool {
|
||||
adminPolicy := cfg.HTTP.AccessControl.AdminPolicy
|
||||
anonymousPolicyPresent := false
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
@ -1447,6 +1448,88 @@ func TestScrub(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestUpdateLDAPConfig(t *testing.T) {
|
||||
Convey("updateLDAPConfig errors while unmarshaling ldap config", t, func() {
|
||||
tempDir := t.TempDir()
|
||||
ldapConfigContent := "bad-json"
|
||||
ldapConfigPath := filepath.Join(tempDir, "ldap.json")
|
||||
|
||||
err := os.WriteFile(ldapConfigPath, []byte(ldapConfigContent), 0o000)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
configStr := fmt.Sprintf(`
|
||||
{
|
||||
"Storage": {
|
||||
"RootDirectory": "%s"
|
||||
},
|
||||
"HTTP": {
|
||||
"Address": "%s",
|
||||
"Port": "%s",
|
||||
"Auth": {
|
||||
"LDAP": {
|
||||
"CredentialsFile": "%s",
|
||||
"BaseDN": "%v",
|
||||
"UserAttribute": "uid",
|
||||
"UserGroupAttribute": "memberOf",
|
||||
"Insecure": true,
|
||||
"Address": "%v",
|
||||
"Port": %v
|
||||
}
|
||||
}
|
||||
}
|
||||
}`, tempDir, "127.0.0.1", "8000", ldapConfigPath, "LDAPBaseDN", "LDAPAddress", 1000)
|
||||
|
||||
configPath := filepath.Join(tempDir, "config.json")
|
||||
|
||||
err = os.WriteFile(configPath, []byte(configStr), 0o0600)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
server := cli.NewServerRootCmd()
|
||||
server.SetArgs([]string{"serve", configPath})
|
||||
So(func() { err = server.Execute() }, ShouldPanic)
|
||||
|
||||
err = os.Chmod(ldapConfigPath, 0o600)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
server = cli.NewServerRootCmd()
|
||||
server.SetArgs([]string{"serve", configPath})
|
||||
So(func() { err = server.Execute() }, ShouldPanic)
|
||||
})
|
||||
|
||||
Convey("unauthenticated LDAP config", t, func() {
|
||||
tempDir := t.TempDir()
|
||||
|
||||
configStr := fmt.Sprintf(`
|
||||
{
|
||||
"Storage": {
|
||||
"RootDirectory": "%s"
|
||||
},
|
||||
"HTTP": {
|
||||
"Address": "%s",
|
||||
"Port": "%s",
|
||||
"Auth": {
|
||||
"LDAP": {
|
||||
"BaseDN": "%v",
|
||||
"UserAttribute": "uid",
|
||||
"UserGroupAttribute": "memberOf",
|
||||
"Insecure": true,
|
||||
"Address": "%v",
|
||||
"Port": %v
|
||||
}
|
||||
}
|
||||
}
|
||||
}`, tempDir, "127.0.0.1", "8000", "LDAPBaseDN", "LDAPAddress", 1000)
|
||||
|
||||
configPath := filepath.Join(tempDir, "config.json")
|
||||
|
||||
err := os.WriteFile(configPath, []byte(configStr), 0o0600)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = cli.LoadConfiguration(config.New(), configPath)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
}
|
||||
|
||||
// run cli and return output.
|
||||
func runCLIWithConfig(tempDir string, config string) (string, error) {
|
||||
port := GetFreePort()
|
||||
|
|
|
@ -225,11 +225,10 @@ func TestMgmtExtension(t *testing.T) {
|
|||
|
||||
Convey("Verify mgmt auth info route enabled with ldap", t, func() {
|
||||
defer os.Remove(conf.HTTP.Auth.HTPasswd.Path) // cleanup of a file created in previous Convey
|
||||
conf.HTTP.Auth.LDAP = &config.LDAPConfig{
|
||||
BindDN: "binddn",
|
||||
conf.HTTP.Auth.LDAP = (&config.LDAPConfig{
|
||||
BaseDN: "basedn",
|
||||
Address: "ldapexample",
|
||||
}
|
||||
}).SetBindDN("binddn")
|
||||
|
||||
conf.Extensions = &extconf.ExtensionConfig{}
|
||||
conf.Extensions.Search = &extconf.SearchConfig{}
|
||||
|
@ -290,11 +289,10 @@ func TestMgmtExtension(t *testing.T) {
|
|||
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(username, password))
|
||||
defer os.Remove(htpasswdPath)
|
||||
conf.HTTP.Auth.HTPasswd.Path = htpasswdPath
|
||||
conf.HTTP.Auth.LDAP = &config.LDAPConfig{
|
||||
BindDN: "binddn",
|
||||
conf.HTTP.Auth.LDAP = (&config.LDAPConfig{
|
||||
BaseDN: "basedn",
|
||||
Address: "ldapexample",
|
||||
}
|
||||
}).SetBindDN("binddn")
|
||||
|
||||
conf.Extensions = &extconf.ExtensionConfig{}
|
||||
conf.Extensions.Search = &extconf.SearchConfig{}
|
||||
|
@ -369,11 +367,10 @@ func TestMgmtExtension(t *testing.T) {
|
|||
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(username, password))
|
||||
defer os.Remove(htpasswdPath)
|
||||
conf.HTTP.Auth.HTPasswd.Path = htpasswdPath
|
||||
conf.HTTP.Auth.LDAP = &config.LDAPConfig{
|
||||
BindDN: "binddn",
|
||||
conf.HTTP.Auth.LDAP = (&config.LDAPConfig{
|
||||
BaseDN: "basedn",
|
||||
Address: "ldapexample",
|
||||
}
|
||||
}).SetBindDN("binddn")
|
||||
|
||||
conf.HTTP.Auth.Bearer = &config.BearerConfig{
|
||||
Realm: "realm",
|
||||
|
@ -449,11 +446,10 @@ func TestMgmtExtension(t *testing.T) {
|
|||
|
||||
Convey("Verify mgmt auth info route enabled with ldap + bearer", t, func() {
|
||||
conf.HTTP.Auth.HTPasswd.Path = ""
|
||||
conf.HTTP.Auth.LDAP = &config.LDAPConfig{
|
||||
BindDN: "binddn",
|
||||
conf.HTTP.Auth.LDAP = (&config.LDAPConfig{
|
||||
BaseDN: "basedn",
|
||||
Address: "ldapexample",
|
||||
}
|
||||
}).SetBindDN("binddn")
|
||||
|
||||
conf.HTTP.Auth.Bearer = &config.BearerConfig{
|
||||
Realm: "realm",
|
||||
|
|
Loading…
Add table
Reference in a new issue