ldap: add initial stub upstream LDAP connection package

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
This commit is contained in:
Andrew Keesler 2021-04-09 11:38:53 -04:00
parent f6ded84f07
commit 4ab704b7de
No known key found for this signature in database
GPG Key ID: 27CE0444346F9413
6 changed files with 268 additions and 0 deletions

1
go.mod
View File

@ -7,6 +7,7 @@ require (
github.com/MakeNowJust/heredoc/v2 v2.0.1
github.com/coreos/go-oidc/v3 v3.0.0
github.com/davecgh/go-spew v1.1.1
github.com/go-ldap/ldap/v3 v3.3.0
github.com/go-logr/logr v0.4.0
github.com/go-logr/stdr v0.4.0
github.com/go-openapi/spec v0.20.3

7
go.sum
View File

@ -49,6 +49,8 @@ github.com/Azure/go-autorest/logger v0.2.0 h1:e4RVHVZKC5p6UANLJHkM4OfR1UKZPj8Wt8
github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28=
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
@ -201,12 +203,16 @@ github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWo
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8=
github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-bindata/go-bindata v3.1.1+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-ldap/ldap/v3 v3.3.0 h1:lwx+SJpgOHd8tG6SumBQZXCmNX51zM8B1cfxJ5gv4tQ=
github.com/go-ldap/ldap/v3 v3.3.0/go.mod h1:iYS1MdmrmceOJ1QOTnRXrIs7i3kloqtmGQjRvjKpyMg=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
@ -1094,6 +1100,7 @@ golang.org/x/crypto v0.0.0-20200117160349-530e935923ad/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20200320181102-891825fb96df/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=

View File

@ -0,0 +1,6 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package mockldapconn
//go:generate go run -v github.com/golang/mock/mockgen -destination=mockldapconn.go -package=mockldapconn -copyright_file=../../../hack/header.txt go.pinniped.dev/internal/upstreamldap Conn

View File

@ -0,0 +1,80 @@
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
//
// Code generated by MockGen. DO NOT EDIT.
// Source: go.pinniped.dev/internal/upstreamldap (interfaces: Conn)
// Package mockldapconn is a generated GoMock package.
package mockldapconn
import (
reflect "reflect"
ldap "github.com/go-ldap/ldap/v3"
gomock "github.com/golang/mock/gomock"
)
// MockConn is a mock of Conn interface.
type MockConn struct {
ctrl *gomock.Controller
recorder *MockConnMockRecorder
}
// MockConnMockRecorder is the mock recorder for MockConn.
type MockConnMockRecorder struct {
mock *MockConn
}
// NewMockConn creates a new mock instance.
func NewMockConn(ctrl *gomock.Controller) *MockConn {
mock := &MockConn{ctrl: ctrl}
mock.recorder = &MockConnMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockConn) EXPECT() *MockConnMockRecorder {
return m.recorder
}
// Bind mocks base method.
func (m *MockConn) Bind(arg0, arg1 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Bind", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// Bind indicates an expected call of Bind.
func (mr *MockConnMockRecorder) Bind(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Bind", reflect.TypeOf((*MockConn)(nil).Bind), arg0, arg1)
}
// Close mocks base method.
func (m *MockConn) Close() {
m.ctrl.T.Helper()
m.ctrl.Call(m, "Close")
}
// Close indicates an expected call of Close.
func (mr *MockConnMockRecorder) Close() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockConn)(nil).Close))
}
// Search mocks base method.
func (m *MockConn) Search(arg0 *ldap.SearchRequest) (*ldap.SearchResult, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Search", arg0)
ret0, _ := ret[0].(*ldap.SearchResult)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Search indicates an expected call of Search.
func (mr *MockConnMockRecorder) Search(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Search", reflect.TypeOf((*MockConn)(nil).Search), arg0)
}

View File

@ -0,0 +1,70 @@
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Package upstreamldap implements an abstraction of upstream LDAP IDP interactions.
package upstreamldap
import (
"context"
ldap "github.com/go-ldap/ldap/v3"
"k8s.io/apiserver/pkg/authentication/authenticator"
)
// Conn abstracts the upstream LDAP communication protocol (mostly for testing).
type Conn interface {
// Bind abstracts ldap.Conn.Bind().
Bind(username, password string) error
// Search abstracts ldap.Conn.Search().
Search(searchRequest *ldap.SearchRequest) (*ldap.SearchResult, error)
// Close abstracts ldap.Conn.Close().
Close()
}
// UserSearch contains information about how to search for users in the upstream LDAP IDP.
type UserSearch struct {
// Base is the base DN to use for the user search in the upstream LDAP IDP.
Base string
// Filter is the filter to use for the user search in the upstream LDAP IDP.
Filter string
// UsernameAttribute is the attribute in the LDAP entry from which the username should be
// retrieved.
UsernameAttribute string
// UIDAttribute is the attribute in the LDAP entry from which the user's unique ID should be
// retrieved.
UIDAttribute string
}
// Provider contains can interact with an upstream LDAP IDP.
type Provider struct {
// Name is the unique name of this upstream LDAP IDP.
Name string
// URL is the URL of this upstream LDAP IDP.
URL string
// Dial is a func that, given a URL, will return an LDAPConn to use for communicating with an
// upstream LDAP IDP.
Dial func(ctx context.Context, url string) (Conn, error)
// BindUsername is the username to use when performing a bind with the upstream LDAP IDP.
BindUsername string
// BindPassword is the password to use when performing a bind with the upstream LDAP IDP.
BindPassword string
// UserSearch contains information about how to search for users in the upstream LDAP IDP.
UserSearch *UserSearch
}
func (p *Provider) GetName() string {
return p.Name
}
func (p *Provider) GetURL() string {
return p.URL
}
func (p *Provider) AuthenticateUser(ctx context.Context, username, password string) (*authenticator.Response, bool, error) {
// TODO: test context timeout?
// TODO: test dial context timeout?
return nil, false, nil
}

View File

@ -0,0 +1,104 @@
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package upstreamldap
import (
"context"
"testing"
ldap "github.com/go-ldap/ldap/v3"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/require"
"k8s.io/apiserver/pkg/authentication/authenticator"
"k8s.io/apiserver/pkg/authentication/user"
"go.pinniped.dev/internal/mocks/mockldapconn"
)
var (
upstreamUsername = "some-upstream-username"
upstreamPassword = "some-upstream-password"
upstreamGroups = []string{"some-upstream-group-0", "some-upstream-group-1"}
upstreamUID = "some-upstream-uid"
)
func TestAuthenticateUser(t *testing.T) {
// Please the linter...
_ = upstreamGroups
_ = upstreamUID
t.Skip("TODO: make me pass!")
tests := []struct {
name string
provider *Provider
wantError string
wantUnauthenticated bool
wantAuthResponse *authenticator.Response
}{
{
name: "happy path",
provider: &Provider{
URL: "ldaps://some-ldap-url:1234",
BindUsername: upstreamUsername,
BindPassword: upstreamPassword,
UserSearch: &UserSearch{
Base: "some-upstream-base-dn",
Filter: "some-filter",
UsernameAttribute: "some-upstream-username-attribute",
UIDAttribute: "some-upstream-uid-attribute",
},
},
wantAuthResponse: &authenticator.Response{
User: &user.DefaultInfo{
Name: upstreamUsername,
Groups: upstreamGroups,
UID: upstreamUID,
},
},
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
t.Cleanup(ctrl.Finish)
conn := mockldapconn.NewMockConn(ctrl)
conn.EXPECT().Bind(test.provider.BindUsername, test.provider.BindPassword).Times(1)
conn.EXPECT().Search(&ldap.SearchRequest{
BaseDN: test.provider.UserSearch.Base,
Scope: 99, // TODO: what should this be?
DerefAliases: 99, // TODO: what should this be?
SizeLimit: 99,
TimeLimit: 99, // TODO: what should this be?
TypesOnly: true, // TODO: what should this be?
Filter: test.provider.UserSearch.Filter,
Attributes: []string{}, // TODO: what should this be?
Controls: []ldap.Control{}, // TODO: what should this be?
}).Return(&ldap.SearchResult{
Entries: []*ldap.Entry{
{
DN: "", // TODO: what should this be?
Attributes: []*ldap.EntryAttribute{}, // TODO: what should this be?
},
},
Referrals: []string{}, // TODO: what should this be?
Controls: []ldap.Control{}, // TODO: what should this be?
}, nil).Times(1)
conn.EXPECT().Close().Times(1)
test.provider.Dial = func(ctx context.Context, url string) (Conn, error) {
require.Equal(t, test.provider.URL, url)
return conn, nil
}
authResponse, authenticated, err := test.provider.AuthenticateUser(context.Background(), upstreamUsername, upstreamPassword)
if test.wantError != "" {
require.EqualError(t, err, test.wantError)
return
}
require.Equal(t, !test.wantUnauthenticated, authenticated)
require.Equal(t, test.wantAuthResponse, authResponse)
})
}
}