Skip to content

Commit

Permalink
feat(ldap): add option to load ldap from file
Browse files Browse the repository at this point in the history
Signed-off-by: Laurentiu Niculae <[email protected]>
  • Loading branch information
laurentiuNiculae committed Oct 30, 2023
1 parent f34af3c commit ad19b55
Show file tree
Hide file tree
Showing 4 changed files with 370 additions and 2 deletions.
10 changes: 8 additions & 2 deletions pkg/api/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,16 +101,22 @@ 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
Expand Down
269 changes: 269 additions & 0 deletions pkg/api/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -2072,6 +2073,178 @@ func TestBasicAuthWithLDAP(t *testing.T) {
})
}

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, passphrase).Get(baseURL)
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusNotFound)

resp, _ = resty.R().SetBasicAuth(username, passphrase).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)
})

Convey("Fail because of not S", 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()

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,
"BindDN": "%v",
"BindPassword": "%v"
}
}
}
}`, tempDir, "127.0.0.1", port, LDAPBaseDN, LDAPAddress, ldapPort, LDAPBindDN, LDAPBindPassword)

configPath := filepath.Join(tempDir, "config.json")

err = os.WriteFile(configPath, []byte(configStr), 0o0600)
So(err, ShouldBeNil)

server := server.NewServerRootCmd()
server.SetArgs([]string{"serve", configPath})

errChan := make(chan error, 1)
timeout := time.After(10 * time.Second)

go func() {
defer func() {
if r := recover(); r != nil {
err, ok := r.(error)
if !ok {
errChan <- fmt.Errorf("returned object from panic is not type 'error', it's type '%T'", r)
}

errChan <- err
}
}()

err := server.Execute()
if err != nil {
errChan <- err
}
}()

for {
select {
case <-timeout:
t.Log("Server is stuck")
t.FailNow()
case err := <-errChan:
So(err.Error(), ShouldContainSubstring, "")
default:
_, err := resty.R().Get(baseURL)
if err != nil {
time.Sleep(500 * time.Millisecond)

break
}

t.Log("Server succesufly loaded when it should have failed")
t.FailNow()
}
}
})
}

func TestGroupsPermissionsForLDAP(t *testing.T) {
Convey("Make a new controller", t, func() {
l := newTestLDAPServer()
Expand Down Expand Up @@ -2140,6 +2313,102 @@ 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": {
"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)

img := CreateDefaultImage()

err = UploadImageWithBasicAuth(
img, baseURL, repo, img.DigestStr(),
username, passphrase)
So(err, ShouldBeNil)
})
}

func TestLDAPFailures(t *testing.T) {
Convey("Make a LDAP conn", t, func() {
l := newTestLDAPServer()
Expand Down
41 changes: 41 additions & 0 deletions pkg/cli/server/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package server

import (
"context"
"encoding/json"
"fmt"
"net"
"net/http"
Expand Down Expand Up @@ -727,6 +728,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)

Expand All @@ -741,6 +746,42 @@ 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 == "" {
return fmt.Errorf("%w: missing CredentialsFile field", zerr.ErrLDAPConfig)
}

newLDAPCredentials, err := readLDAPCredentials(conf.HTTP.Auth.LDAP.CredentialsFile)
if err != nil {
return err
}

conf.HTTP.Auth.LDAP.BindDN = newLDAPCredentials.BindDN
conf.HTTP.Auth.LDAP.BindPassword = newLDAPCredentials.BindPassword

return nil
}

func readLDAPCredentials(ldapConfigPath string) (config.LDAPCredentials, error) {
ldapConfigBlob, err := os.ReadFile(ldapConfigPath)
if err != nil {
return config.LDAPCredentials{}, err
}

var ldapCredentials config.LDAPCredentials

err = json.Unmarshal(ldapConfigBlob, &ldapCredentials)
if err != nil {
return config.LDAPCredentials{}, err
}

return ldapCredentials, nil
}

func authzContainsOnlyAnonymousPolicy(cfg *config.Config) bool {
adminPolicy := cfg.HTTP.AccessControl.AdminPolicy
anonymousPolicyPresent := false
Expand Down
Loading

0 comments on commit ad19b55

Please sign in to comment.