diff --git a/README.md b/README.md index f7e72e4..4f3ce91 100644 --- a/README.md +++ b/README.md @@ -13,10 +13,10 @@ To build and run from the command line: * Clone this repo * Run `dep ensure` (must have [dep](https://github.com/golang/dep) installed ) * Run `go build` -* Run `FLYTE_API_URL= BIND_USERNAME= BIND_PASSWORD= LDAP_URL= GROUP_ATTRIBUTE= ATTRIBUTES= BASE_DN= SEARCH_FILTER= SEARCH_TIMEOUT_IN_SECONDS= ./flyte-ldap` +* Run `FLYTE_API_URL= BIND_USERNAME= BIND_PASSWORD= LDAP_URL= GROUP_ATTRIBUTE= ATTRIBUTES= BASE_DN= SEARCH_FILTER= SEARCH_TIMEOUT_IN_SECONDS= ENABLE_TLS= INSECURE_SKIP_VERIFY= ./flyte-ldap` * All of these environment variables need to be provided with the exception of 'SEARCH_TIMEOUT_IN_SECONDS', which has a default (see main.go). #### Example -* Run `FLYTE_API_URL='http://myflyteapi.com' BIND_USERNAME='someUsername' BIND_PASSWORD='somePassword' LDAP_URL='my.ldap.com:123' GROUP_ATTRIBUTE='cn' ATTRIBUTES='memberOf' BASE_DN='DC=QQ,DC=WOW,DC=XYZ,DC=com' SEARCH_FILTER='(mailNickname={username})' SEARCH_TIMEOUT_IN_SECONDS='20' ./flyte-ldap` +* Run `FLYTE_API_URL='http://myflyteapi.com' BIND_USERNAME='someUsername' BIND_PASSWORD='somePassword' LDAP_URL='my.ldap.com:123' GROUP_ATTRIBUTE='cn' ATTRIBUTES='memberOf' BASE_DN='DC=QQ,DC=WOW,DC=XYZ,DC=com' SEARCH_FILTER='(mailNickname={username})' SEARCH_TIMEOUT_IN_SECONDS='20' ENABLE_TLS= INSECURE_SKIP_VERIFY= ./flyte-ldap` ### Run tests To run the unit tests: @@ -25,10 +25,10 @@ To run the unit tests: ### Docker To build and run from docker * Run `docker build -t flyte-ldap .` -* Run `docker run -e FLYTE_API_URL= -e BIND_USERNAME= -e BIND_PASSWORD= -e LDAP_URL= -e GROUP_ATTRIBUTE= -e ATTRIBUTES= -e BASE_DN= -e SEARCH_FILTER= -e SEARCH_TIMEOUT_IN_SECONDS= flyte-ldap` +* Run `docker run -e FLYTE_API_URL= -e BIND_USERNAME= -e BIND_PASSWORD= -e LDAP_URL= -e GROUP_ATTRIBUTE= -e ATTRIBUTES= -e BASE_DN= -e SEARCH_FILTER= -e SEARCH_TIMEOUT_IN_SECONDS= -e ENABLE_TLS='True' -e INSECURE_SKIP_VERIFY='True' flyte-ldap` * All of these environment variables need to be provided with the exception of 'SEARCH_TIMEOUT_IN_SECONDS', which has a default (see main.go). #### Example -* Run `docker run -e FLYTE_API_URL='http://myflyteapi.com' -e BIND_USERNAME='someUsername' -e BIND_PASSWORD='somePassword' -e LDAP_URL='my.ldap.com:123' -e GROUP_ATTRIBUTE='cn' -e ATTRIBUTES='memberOf' -e BASE_DN='DC=QQ,DC=WOW,DC=XYZ,DC=com' -e SEARCH_FILTER='(mailNickname={username})' -e SEARCH_TIMEOUT_IN_SECONDS='20' flyte-ldap` +* Run `docker run -e FLYTE_API_URL='http://myflyteapi.com' -e BIND_USERNAME='someUsername' -e BIND_PASSWORD='somePassword' -e LDAP_URL='my.ldap.com:123' -e GROUP_ATTRIBUTE='cn' -e ATTRIBUTES='memberOf' -e BASE_DN='DC=QQ,DC=WOW,DC=XYZ,DC=com' -e SEARCH_FILTER='(mailNickname={username})' -e SEARCH_TIMEOUT_IN_SECONDS='20' -e ENABLE_TLS='True' -e INSECURE_SKIP_VERIFY='True' flyte-ldap` #### LDAP Attribute explanation diff --git a/group/search.go b/group/search.go index c7338e5..600115f 100644 --- a/group/search.go +++ b/group/search.go @@ -23,11 +23,13 @@ import ( ) type SearchDetails struct { - Attributes []string // i.e. the attributes to be returned by the group, e.g. 'memberOf' - BaseDn string - SearchFilter string - GroupAttribute string // the attribute that gives the name of the group from the attribute values, e.g. 'cn' - SearchTimeout int + Attributes []string // i.e. the attributes to be returned by the group, e.g. 'memberOf' + BaseDn string + SearchFilter string + GroupAttribute string // the attribute that gives the name of the group from the attribute values, e.g. 'cn' + SearchTimeout int + EnableTLS bool + InsecureSkipVerify bool } type Searcher interface { @@ -43,7 +45,13 @@ func NewSearcher(client ldap.Client) Searcher { } func (searcher *searcher) GetGroupsFor(sd *SearchDetails, username string) ([]string, error) { - if err := searcher.client.Connect(); err != nil { + var err error + if sd.EnableTLS == true { + err = searcher.client.ConnectTls(sd.InsecureSkipVerify) + } else { + err = searcher.client.Connect() + } + if err != nil { return nil, err } defer searcher.client.Close() diff --git a/group/search_test.go b/group/search_test.go index b8a8558..cc444d7 100644 --- a/group/search_test.go +++ b/group/search_test.go @@ -32,6 +32,10 @@ func TestClientConnectAndCloseAreCalledWhenCallingGetGroupsFor(t *testing.T) { isClientConnectCalled = true return nil }, + connectTls: func(insecureSkipVerify bool) error { + isClientConnectCalled = true + return nil + }, close: func() { isClientCloseCalled = true }, @@ -59,6 +63,9 @@ func TestErrorIsReturnedIfClientConnectError(t *testing.T) { connect: func() error { return errors.New("Meh") }, + connectTls: func(insecureSkipVerify bool) error { + return errors.New("Meh") + }, } searcher := NewSearcher(mockClient) searchDetails := &SearchDetails{} @@ -91,7 +98,10 @@ func TestSearchShouldReturnsUserGroups(t *testing.T) { } mockClient := &mockClient{ connect: func() error { return nil }, - close: func() {}, + connectTls: func(insecureSkipVerify bool) error { + return nil + }, + close: func() {}, search: func(sr ldap.SearchRequest) (*ldapClient.SearchResult, error) { return returnedSearchResults, nil }} @@ -120,7 +130,10 @@ func TestSearchShouldReturnsUserGroups(t *testing.T) { func TestSearchShouldNotReturnsUserGroupsIfNoSearchResultsAreReturned(t *testing.T) { mockClient := &mockClient{ connect: func() error { return nil }, - close: func() {}, + connectTls: func(insecureSkipVerify bool) error { + return nil + }, + close: func() {}, search: func(sr ldap.SearchRequest) (*ldapClient.SearchResult, error) { return &ldapClient.SearchResult{}, nil }} @@ -139,8 +152,10 @@ func TestSearchShouldNotReturnsUserGroupsIfNoSearchResultsAreReturned(t *testing func TestSearchShouldReturnClientSearchError(t *testing.T) { mockClient := &mockClient{ - connect: func() error { return nil }, - close: func() {}, + connect: func() error { return nil }, + connectTls: func(insecureSkipVerify bool) error { return nil }, + + close: func() {}, search: func(sr ldap.SearchRequest) (*ldapClient.SearchResult, error) { return nil, errors.New("Some error") }} @@ -160,8 +175,9 @@ func TestSearchShouldReturnClientSearchError(t *testing.T) { func TestCorrectParametersArePassedToClientSearch(t *testing.T) { var searchRequest ldap.SearchRequest mockClient := &mockClient{ - connect: func() error { return nil }, - close: func() {}, + connect: func() error { return nil }, + connectTls: func(insecureSkipVerify bool) error { return nil }, + close: func() {}, search: func(sr ldap.SearchRequest) (*ldapClient.SearchResult, error) { searchRequest = sr return &ldapClient.SearchResult{}, nil @@ -239,9 +255,14 @@ func someSearchDetails() *SearchDetails { } type mockClient struct { - connect func() error - close func() - search func(sr ldap.SearchRequest) (*ldapClient.SearchResult, error) + connect func() error + connectTls func(insecureSkipVerify bool) error + close func() + search func(sr ldap.SearchRequest) (*ldapClient.SearchResult, error) +} + +func (c *mockClient) ConnectTls(insecureSkipVerify bool) error { + return c.connectTls(true) } func (c *mockClient) Connect() error { diff --git a/ldap/client.go b/ldap/client.go index d532271..45b214f 100644 --- a/ldap/client.go +++ b/ldap/client.go @@ -17,12 +17,14 @@ limitations under the License. package ldap import ( + "crypto/tls" "fmt" "gopkg.in/ldap.v2" ) type Client interface { Connect() error + ConnectTls(insecureSkipVerify bool) error Search(sr SearchRequest) (*ldap.SearchResult, error) Close() } @@ -71,6 +73,23 @@ func (c *ldapClient) Connect() error { return nil } +func (c *ldapClient) ConnectTls(insecureSkipVerify bool) error { + ldapConn, err := ldap.DialTLS("tcp", c.ldapServerUrl, &tls.Config{InsecureSkipVerify: insecureSkipVerify}) + if err != nil { + return fmt.Errorf("Cannot connect to LDAP: %v", err) + } + + err = ldapConn.Bind(c.bindUsername, c.bindPassword) + if err != nil { + ldapConn.Close() + return fmt.Errorf("Cannot bind to LDAP: %v", err) + } + + c.ldapSearcher = ldapConn + + return nil +} + func (c *ldapClient) Search(sr SearchRequest) (*ldap.SearchResult, error) { searchRequest := &ldap.SearchRequest{ BaseDN: sr.BaseDn, diff --git a/main.go b/main.go index a353ac4..ff2527f 100644 --- a/main.go +++ b/main.go @@ -47,15 +47,25 @@ func main() { if err != nil { logger.Fatalf("LDAP group timeout '%v' not convertible to an integer. Error: %v", configVal("SEARCH_TIMEOUT_IN_SECONDS"), err) } + tlsEnabledFlag, err := strconv.ParseBool(configVal("ENABLE_TLS")) + if err != nil { + logger.Fatalf("TLS enabled flag is not provided '%v' Error: %v", configVal("ENABLE_TLS"), err) + } + insecureSkipVerify, err := strconv.ParseBool(configVal("INSECURE_SKIP_VERIFY")) + if err != nil { + insecureSkipVerify = false + } lc := ldap.NewClient(configVal("BIND_USERNAME"), configVal("BIND_PASSWORD"), configVal("LDAP_URL")) searcher := group.NewSearcher(lc) searchDetails := &group.SearchDetails{ - Attributes: strings.Split(configVal("ATTRIBUTES"), ","), - BaseDn: configVal("BASE_DN"), - SearchFilter: configVal("SEARCH_FILTER"), - SearchTimeout: searchTimeout, - GroupAttribute: configVal("GROUP_ATTRIBUTE"), + Attributes: strings.Split(configVal("ATTRIBUTES"), ","), + BaseDn: configVal("BASE_DN"), + SearchFilter: configVal("SEARCH_FILTER"), + SearchTimeout: searchTimeout, + GroupAttribute: configVal("GROUP_ATTRIBUTE"), + EnableTLS: tlsEnabledFlag, + InsecureSkipVerify: insecureSkipVerify, } packDef := flyte.PackDef{