mirror of
https://github.com/project-zot/zot.git
synced 2024-12-30 22:34:13 -05:00
parent
be7ce56343
commit
6295e0c91e
15 changed files with 634 additions and 48 deletions
4
Makefile
4
Makefile
|
@ -22,9 +22,9 @@ test:
|
|||
go test -v -race -cover -coverprofile=coverage.txt -covermode=atomic ./...
|
||||
|
||||
.PHONY: check
|
||||
check:
|
||||
check: .bazel/golangcilint.yaml
|
||||
golangci-lint --version || curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s v1.17.1
|
||||
golangci-lint run --enable-all ./cmd/... ./pkg/...
|
||||
golangci-lint --config .bazel/golangcilint.yaml run --enable-all ./cmd/... ./pkg/...
|
||||
|
||||
docs/docs.go:
|
||||
swag -v || go install github.com/swaggo/swag/cmd/swag
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
* Conforms to [OCI distribution spec](https://github.com/opencontainers/distribution-spec) APIs
|
||||
* Uses [OCI storage layout](https://github.com/opencontainers/image-spec/blob/master/image-layout.md) for storage layout
|
||||
* TLS support
|
||||
* *Basic* and TLS mutual authentication
|
||||
* Swagger based documentation
|
||||
* Authentication via TLS mutual authentication and HTTP *BASIC* (local _htpasswd_ and LDAP)
|
||||
* Doesn't require _root_ privileges
|
||||
* Swagger based documentation
|
||||
|
||||
# Building
|
||||
|
||||
|
|
105
WORKSPACE
105
WORKSPACE
|
@ -1008,3 +1008,108 @@ go_repository(
|
|||
sum = "h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=",
|
||||
version = "v0.0.0-20190717185122-a985d3407aa7",
|
||||
)
|
||||
|
||||
go_repository(
|
||||
name = "com_github_boombuler_barcode",
|
||||
importpath = "github.com/boombuler/barcode",
|
||||
sum = "h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=",
|
||||
version = "v1.0.1-0.20190219062509-6c824513bacc",
|
||||
)
|
||||
|
||||
go_repository(
|
||||
name = "com_github_docopt_docopt_go",
|
||||
importpath = "github.com/docopt/docopt-go",
|
||||
sum = "h1:bWDMxwH3px2JBh6AyO7hdCn/PkvCZXii8TGj7sbtEbQ=",
|
||||
version = "v0.0.0-20180111231733-ee0de3bc6815",
|
||||
)
|
||||
|
||||
go_repository(
|
||||
name = "com_github_geertjohan_yubigo",
|
||||
importpath = "github.com/GeertJohan/yubigo",
|
||||
sum = "h1:KA/G9j1p6mBmMihAZwmpnS6t8WsToyVlvF2v5VgJIcY=",
|
||||
version = "v0.0.0-20190829090426-2d4089dc8789",
|
||||
)
|
||||
|
||||
go_repository(
|
||||
name = "com_github_glauth_glauth",
|
||||
importpath = "github.com/glauth/glauth",
|
||||
sum = "h1:2Rl5vTPWlchM4P+VCUtHbD7U3wFcoLYZiTwYad2QCOM=",
|
||||
version = "v1.1.1",
|
||||
)
|
||||
|
||||
go_repository(
|
||||
name = "com_github_jtblin_go_ldap_client",
|
||||
importpath = "github.com/jtblin/go-ldap-client",
|
||||
sum = "h1:XDpFOMOZq0u0Ar4F0p/wklqQXp/AMV1pTF5T5bDoUfQ=",
|
||||
version = "v0.0.0-20170223121919-b73f66626b33",
|
||||
)
|
||||
|
||||
go_repository(
|
||||
name = "com_github_nmcclain_asn1_ber",
|
||||
importpath = "github.com/nmcclain/asn1-ber",
|
||||
sum = "h1:D9EvfGQvlkKaDr2CRKN++7HbSXbefUNDrPq60T+g24s=",
|
||||
version = "v0.0.0-20170104154839-2661553a0484",
|
||||
)
|
||||
|
||||
go_repository(
|
||||
name = "com_github_nmcclain_ldap",
|
||||
importpath = "github.com/nmcclain/ldap",
|
||||
sum = "h1:SNpbw8iNcHdnboQsLB5wkRAgCSqWXplItrd8Xxu+9Dc=",
|
||||
version = "v0.0.0-20190703182433-09931d85c0ff",
|
||||
)
|
||||
|
||||
go_repository(
|
||||
name = "com_github_op_go_logging",
|
||||
importpath = "github.com/op/go-logging",
|
||||
sum = "h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=",
|
||||
version = "v0.0.0-20160315200505-970db520ece7",
|
||||
)
|
||||
|
||||
go_repository(
|
||||
name = "com_github_pquerna_otp",
|
||||
importpath = "github.com/pquerna/otp",
|
||||
sum = "h1:/A3+Jn+cagqayeR3iHs/L62m5ue7710D35zl1zJ1kok=",
|
||||
version = "v1.2.0",
|
||||
)
|
||||
|
||||
go_repository(
|
||||
name = "com_github_samuel_go_ldap",
|
||||
importpath = "github.com/samuel/go-ldap",
|
||||
sum = "h1:1iey3/nAwh5WYP9DGAH6vZGyBhCbRZ0fkX33LO138Fg=",
|
||||
version = "v0.0.0-20150819063227-09b1a56d2755",
|
||||
)
|
||||
|
||||
go_repository(
|
||||
name = "com_github_vjeantet_ldapserver",
|
||||
importpath = "github.com/vjeantet/ldapserver",
|
||||
sum = "h1:VWE8ZC9ER1YIfx0V0QgZGdG4UB/nGeaSmKxesfVuheo=",
|
||||
version = "v0.0.0-20170919170217-479fece7c5f1",
|
||||
)
|
||||
|
||||
go_repository(
|
||||
name = "in_gopkg_amz_v1",
|
||||
importpath = "gopkg.in/amz.v1",
|
||||
sum = "h1:FMrsB0OTjHsPDA1NM7AhRmmZzkBPu3iGdxK/5MFfBmk=",
|
||||
version = "v1.0.0-20150111123259-ad23e96a31d2",
|
||||
)
|
||||
|
||||
go_repository(
|
||||
name = "in_gopkg_asn1_ber_v1",
|
||||
importpath = "gopkg.in/asn1-ber.v1",
|
||||
sum = "h1:TxyelI5cVkbREznMhfzycHdkp5cLA7DpE+GKjSslYhM=",
|
||||
version = "v1.0.0-20181015200546-f715ec2f112d",
|
||||
)
|
||||
|
||||
go_repository(
|
||||
name = "in_gopkg_ldap_v2",
|
||||
importpath = "gopkg.in/ldap.v2",
|
||||
sum = "h1:wiu0okdNfjlBzg6UWvd1Hn8Y+Ux17/u/4nlk4CQr6tU=",
|
||||
version = "v2.5.1",
|
||||
)
|
||||
|
||||
go_repository(
|
||||
name = "com_github_getlantern_deepcopy",
|
||||
importpath = "github.com/getlantern/deepcopy",
|
||||
sum = "h1:yU/FENpkHYISWsQrbr3pcZOBj0EuRjPzNc1+dTCLu44=",
|
||||
version = "v0.0.0-20160317154340-7f45deb8130a",
|
||||
)
|
||||
|
|
|
@ -16,4 +16,7 @@ var (
|
|||
ErrBadBlobDigest = errors.New("blob: bad blob digest")
|
||||
ErrUnknownCode = errors.New("error: unknown error code")
|
||||
ErrBadCACert = errors.New("tls: invalid ca cert")
|
||||
ErrBadUser = errors.New("ldap: non-existent user")
|
||||
ErrEntriesExceeded = errors.New("ldap: too many entries returned")
|
||||
ErrLDAPConfig = errors.New("config: invalid LDAP configuration")
|
||||
)
|
||||
|
|
|
@ -12,6 +12,17 @@
|
|||
"key":"test/data/server.key"
|
||||
},
|
||||
"auth": {
|
||||
"ldap": {
|
||||
"address":"ldap.example.org",
|
||||
"port":389,
|
||||
"startTLS":false,
|
||||
"baseDN":"ou=Users,dc=example,dc=org",
|
||||
"userAttribute":"uid",
|
||||
"bindDN":"cn=ldap-searcher,ou=Users,dc=example,dc=org",
|
||||
"bindPassword":"ldap-searcher-password",
|
||||
"skipVerify":false,
|
||||
"subtreeSearch":true
|
||||
},
|
||||
"htpasswd": {
|
||||
"path": "test/data/htpasswd"
|
||||
},
|
||||
|
|
|
@ -11,6 +11,16 @@ http:
|
|||
cert: test/data/server.cert
|
||||
key: test/data/server.key
|
||||
auth:
|
||||
ldap:
|
||||
address: ldap.example.org
|
||||
port: 389
|
||||
startTLS: false
|
||||
baseDN: ou=Users,dc=example,dc=org
|
||||
userAttribute: uid
|
||||
bindDN: cn=ldap-searcher,ou=Users,dc=example,dc=org
|
||||
bindPassword: ldap-searcher-password
|
||||
skipVerify: false
|
||||
subtreeSearch: true
|
||||
htpasswd:
|
||||
path: test/data/htpasswd
|
||||
failDelay: 5
|
||||
|
|
6
go.mod
6
go.mod
|
@ -4,13 +4,17 @@ go 1.12
|
|||
|
||||
require (
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc
|
||||
github.com/getlantern/deepcopy v0.0.0-20160317154340-7f45deb8130a
|
||||
github.com/go-chi/chi v4.0.2+incompatible // indirect
|
||||
github.com/gofrs/uuid v3.2.0+incompatible
|
||||
github.com/gorilla/mux v1.7.3
|
||||
github.com/json-iterator/go v1.1.6
|
||||
github.com/jtblin/go-ldap-client v0.0.0-20170223121919-b73f66626b33
|
||||
github.com/mitchellh/mapstructure v1.1.2
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.1 // indirect
|
||||
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484 // indirect
|
||||
github.com/nmcclain/ldap v0.0.0-20190703182433-09931d85c0ff
|
||||
github.com/opencontainers/distribution-spec v1.0.0-rc0
|
||||
github.com/opencontainers/go-digest v1.0.0-rc1
|
||||
github.com/opencontainers/image-spec v1.0.1
|
||||
|
@ -26,5 +30,7 @@ require (
|
|||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456 // indirect
|
||||
golang.org/x/text v0.3.2 // indirect
|
||||
golang.org/x/tools v0.0.0-20190827205025-b29f5f60c37a // indirect
|
||||
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect
|
||||
gopkg.in/ldap.v2 v2.5.1
|
||||
gopkg.in/resty.v1 v1.12.0
|
||||
)
|
||||
|
|
12
go.sum
12
go.sum
|
@ -30,6 +30,8 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm
|
|||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/getlantern/deepcopy v0.0.0-20160317154340-7f45deb8130a h1:yU/FENpkHYISWsQrbr3pcZOBj0EuRjPzNc1+dTCLu44=
|
||||
github.com/getlantern/deepcopy v0.0.0-20160317154340-7f45deb8130a/go.mod h1:AEugkNu3BjBxyz958nJ5holD9PRjta6iprcoUauDbU4=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-chi/chi v4.0.2+incompatible h1:maB6vn6FqCxrpz4FqWdh4+lwpyZIQS7YEAUcHlgXVRs=
|
||||
github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
|
@ -72,6 +74,8 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt
|
|||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/jtblin/go-ldap-client v0.0.0-20170223121919-b73f66626b33 h1:XDpFOMOZq0u0Ar4F0p/wklqQXp/AMV1pTF5T5bDoUfQ=
|
||||
github.com/jtblin/go-ldap-client v0.0.0-20170223121919-b73f66626b33/go.mod h1:+0BcLY5d54TVv6irFzHoiFvwAHR6T0g9B+by/UaS9T0=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
|
@ -97,6 +101,10 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
|
|||
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484 h1:D9EvfGQvlkKaDr2CRKN++7HbSXbefUNDrPq60T+g24s=
|
||||
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484/go.mod h1:O1EljZ+oHprtxDDPHiMWVo/5dBT6PlvWX5PSwj80aBA=
|
||||
github.com/nmcclain/ldap v0.0.0-20190703182433-09931d85c0ff h1:SNpbw8iNcHdnboQsLB5wkRAgCSqWXplItrd8Xxu+9Dc=
|
||||
github.com/nmcclain/ldap v0.0.0-20190703182433-09931d85c0ff/go.mod h1:YtrVB1/v9Td9SyjXpjYVmbdKgj9B0nPTBsdGUxy0i8U=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/opencontainers/distribution-spec v1.0.0-rc0 h1:xMzwhweo1gjvEo74mQjGTLau0TD3ACyTEC1310NbuSQ=
|
||||
github.com/opencontainers/distribution-spec v1.0.0-rc0/go.mod h1:copR2flp+jTEvQIFMb6MIx45OkrxzqyjszPDT3hx/5Q=
|
||||
|
@ -221,9 +229,13 @@ google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoA
|
|||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d h1:TxyelI5cVkbREznMhfzycHdkp5cLA7DpE+GKjSslYhM=
|
||||
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/ldap.v2 v2.5.1 h1:wiu0okdNfjlBzg6UWvd1Hn8Y+Ux17/u/4nlk4CQr6tU=
|
||||
gopkg.in/ldap.v2 v2.5.1/go.mod h1:oI0cpe/D7HRtBQl8aTg+ZmzFUAvu4lsv3eLXMLGFxWk=
|
||||
gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
|
|
|
@ -7,6 +7,7 @@ go_library(
|
|||
"config.go",
|
||||
"controller.go",
|
||||
"errors.go",
|
||||
"ldap.go",
|
||||
"log.go",
|
||||
"regexp.go",
|
||||
"routes.go",
|
||||
|
@ -17,12 +18,15 @@ go_library(
|
|||
"//docs:go_default_library",
|
||||
"//errors:go_default_library",
|
||||
"//pkg/storage:go_default_library",
|
||||
"@com_github_getlantern_deepcopy//:go_default_library",
|
||||
"@com_github_gorilla_mux//:go_default_library",
|
||||
"@com_github_json_iterator_go//:go_default_library",
|
||||
"@com_github_jtblin_go_ldap_client//:go_default_library",
|
||||
"@com_github_opencontainers_distribution_spec//:go_default_library",
|
||||
"@com_github_opencontainers_image_spec//specs-go/v1:go_default_library",
|
||||
"@com_github_rs_zerolog//:go_default_library",
|
||||
"@com_github_swaggo_http_swagger//:go_default_library",
|
||||
"@in_gopkg_ldap_v2//:go_default_library",
|
||||
"@org_golang_x_crypto//bcrypt:go_default_library",
|
||||
],
|
||||
)
|
||||
|
@ -40,6 +44,7 @@ go_test(
|
|||
embed = [":go_default_library"],
|
||||
race = "on",
|
||||
deps = [
|
||||
"@com_github_nmcclain_ldap//:go_default_library",
|
||||
"@com_github_opencontainers_go_digest//:go_default_library",
|
||||
"@com_github_opencontainers_image_spec//specs-go/v1:go_default_library",
|
||||
"@com_github_smartystreets_goconvey//convey:go_default_library",
|
||||
|
|
|
@ -2,14 +2,19 @@ package api
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/anuvu/zot/errors"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/jtblin/go-ldap-client"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
|
@ -20,25 +25,25 @@ func authFail(w http.ResponseWriter, realm string, delay int) {
|
|||
WriteJSON(w, http.StatusUnauthorized, NewError(UNAUTHORIZED))
|
||||
}
|
||||
|
||||
// nolint (gocyclo) - we use closure making this a complex subroutine
|
||||
func BasicAuthHandler(c *Controller) mux.MiddlewareFunc {
|
||||
realm := c.Config.HTTP.Realm
|
||||
if realm == "" {
|
||||
realm = "Authorization Required"
|
||||
}
|
||||
realm = "Basic realm=" + strconv.Quote(realm)
|
||||
delay := c.Config.HTTP.Auth.FailDelay
|
||||
|
||||
if c.Config.HTTP.Auth.HTPasswd.Path == "" {
|
||||
// no password based authN, if neither LDAP nor HTTP BASIC is enabled
|
||||
if c.Config.HTTP.Auth == nil || (c.Config.HTTP.Auth.HTPasswd.Path == "" && c.Config.HTTP.Auth.LDAP == nil) {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if c.Config.HTTP.AllowReadAccess &&
|
||||
c.Config.HTTP.TLS.CACert != "" &&
|
||||
r.TLS.VerifiedChains == nil &&
|
||||
r.Method != "GET" && r.Method != "HEAD" {
|
||||
authFail(w, realm, delay)
|
||||
authFail(w, realm, 5)
|
||||
return
|
||||
}
|
||||
|
||||
// Process request
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
|
@ -46,20 +51,63 @@ func BasicAuthHandler(c *Controller) mux.MiddlewareFunc {
|
|||
}
|
||||
|
||||
credMap := make(map[string]string)
|
||||
delay := c.Config.HTTP.Auth.FailDelay
|
||||
var ldapClient *LDAPClient
|
||||
|
||||
f, err := os.Open(c.Config.HTTP.Auth.HTPasswd.Path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for {
|
||||
r := bufio.NewReader(f)
|
||||
line, err := r.ReadString('\n')
|
||||
if err != nil {
|
||||
break
|
||||
if c.Config.HTTP.Auth != nil {
|
||||
if c.Config.HTTP.Auth.LDAP != nil {
|
||||
l := c.Config.HTTP.Auth.LDAP
|
||||
ldapClient = &LDAPClient{
|
||||
LDAPClient: ldap.LDAPClient{
|
||||
Host: l.Address,
|
||||
Port: l.Port,
|
||||
UseSSL: !l.Insecure,
|
||||
SkipTLS: !l.StartTLS,
|
||||
Base: l.BaseDN,
|
||||
BindDN: l.BindDN,
|
||||
BindPassword: l.BindPassword,
|
||||
UserFilter: fmt.Sprintf("(%s=%%s)", l.UserAttribute),
|
||||
InsecureSkipVerify: l.SkipVerify,
|
||||
ServerName: l.Address,
|
||||
},
|
||||
log: c.Log,
|
||||
subtreeSearch: l.SubtreeSearch,
|
||||
}
|
||||
if c.Config.HTTP.Auth.LDAP.CACert != "" {
|
||||
caCert, err := ioutil.ReadFile(c.Config.HTTP.Auth.LDAP.CACert)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
caCertPool := x509.NewCertPool()
|
||||
if !caCertPool.AppendCertsFromPEM(caCert) {
|
||||
panic(errors.ErrBadCACert)
|
||||
}
|
||||
ldapClient.clientCAs = caCertPool
|
||||
} else {
|
||||
// default to system cert pool
|
||||
caCertPool, err := x509.SystemCertPool()
|
||||
if err != nil {
|
||||
panic(errors.ErrBadCACert)
|
||||
}
|
||||
ldapClient.clientCAs = caCertPool
|
||||
}
|
||||
}
|
||||
if c.Config.HTTP.Auth.HTPasswd.Path != "" {
|
||||
f, err := os.Open(c.Config.HTTP.Auth.HTPasswd.Path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for {
|
||||
r := bufio.NewReader(f)
|
||||
line, err := r.ReadString('\n')
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
tokens := strings.Split(line, ":")
|
||||
credMap[tokens[0]] = tokens[1]
|
||||
}
|
||||
}
|
||||
tokens := strings.Split(line, ":")
|
||||
credMap[tokens[0]] = tokens[1]
|
||||
}
|
||||
|
||||
return func(next http.Handler) http.Handler {
|
||||
|
@ -97,6 +145,17 @@ func BasicAuthHandler(c *Controller) mux.MiddlewareFunc {
|
|||
username := pair[0]
|
||||
passphrase := pair[1]
|
||||
|
||||
// prefer LDAP if configured
|
||||
if c.Config.HTTP.Auth != nil && c.Config.HTTP.Auth.LDAP != nil {
|
||||
ok, _, err := ldapClient.Authenticate(username, passphrase)
|
||||
if ok && err == nil {
|
||||
// Process request
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// fallback to HTTPPassword
|
||||
passphraseHash, ok := credMap[username]
|
||||
if !ok {
|
||||
authFail(w, realm, delay)
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"github.com/anuvu/zot/errors"
|
||||
"github.com/getlantern/deepcopy"
|
||||
dspec "github.com/opencontainers/distribution-spec"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
//nolint (gochecknoglobals)
|
||||
|
@ -24,17 +27,32 @@ type AuthHTPasswd struct {
|
|||
type AuthConfig struct {
|
||||
FailDelay int
|
||||
HTPasswd AuthHTPasswd
|
||||
LDAP *LDAPConfig
|
||||
}
|
||||
|
||||
type HTTPConfig struct {
|
||||
Address string
|
||||
Port string
|
||||
TLS TLSConfig `mapstructure:",omitempty"`
|
||||
Auth AuthConfig `mapstructure:",omitempty"`
|
||||
TLS *TLSConfig
|
||||
Auth *AuthConfig
|
||||
Realm string
|
||||
AllowReadAccess bool `mapstructure:",omitempty"`
|
||||
}
|
||||
|
||||
type LDAPConfig struct {
|
||||
Port int
|
||||
Insecure bool
|
||||
StartTLS bool // if !Insecure, then StartTLS or LDAPs
|
||||
SkipVerify bool
|
||||
SubtreeSearch bool
|
||||
Address string
|
||||
BindDN string
|
||||
BindPassword string
|
||||
BaseDN string
|
||||
UserAttribute string
|
||||
CACert string
|
||||
}
|
||||
|
||||
type LogConfig struct {
|
||||
Level string
|
||||
Output string
|
||||
|
@ -45,7 +63,7 @@ type Config struct {
|
|||
Commit string
|
||||
Storage StorageConfig
|
||||
HTTP HTTPConfig
|
||||
Log LogConfig `mapstructure:",omitempty"`
|
||||
Log *LogConfig
|
||||
}
|
||||
|
||||
func NewConfig() *Config {
|
||||
|
@ -53,6 +71,35 @@ func NewConfig() *Config {
|
|||
Version: dspec.Version,
|
||||
Commit: Commit,
|
||||
HTTP: HTTPConfig{Address: "127.0.0.1", Port: "8080"},
|
||||
Log: LogConfig{Level: "debug"},
|
||||
Log: &LogConfig{Level: "debug"},
|
||||
}
|
||||
}
|
||||
|
||||
// Sanitize makes a sanitized copy of the config removing any secrets
|
||||
func (c *Config) Sanitize() *Config {
|
||||
if c.HTTP.Auth != nil && c.HTTP.Auth.LDAP != nil && c.HTTP.Auth.LDAP.BindPassword != "" {
|
||||
s := &Config{}
|
||||
if err := deepcopy.Copy(s, c); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
s.HTTP.Auth.LDAP = &LDAPConfig{}
|
||||
if err := deepcopy.Copy(s.HTTP.Auth.LDAP, c.HTTP.Auth.LDAP); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
s.HTTP.Auth.LDAP.BindPassword = "******"
|
||||
return s
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Config) Validate(log zerolog.Logger) error {
|
||||
// LDAP configuration
|
||||
if c.HTTP.Auth != nil && c.HTTP.Auth.LDAP != nil {
|
||||
l := c.HTTP.Auth.LDAP
|
||||
if l.UserAttribute == "" {
|
||||
log.Error().Str("userAttribute", l.UserAttribute).Msg("invalid LDAP configuration")
|
||||
return errors.ErrLDAPConfig
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -27,12 +27,20 @@ func NewController(config *Config) *Controller {
|
|||
}
|
||||
|
||||
func (c *Controller) Run() error {
|
||||
// validate configuration
|
||||
if err := c.Config.Validate(c.Log); err != nil {
|
||||
c.Log.Error().Err(err).Msg("configuration validation failed")
|
||||
return err
|
||||
}
|
||||
|
||||
// print the current configuration, but strip secrets
|
||||
c.Log.Info().Interface("params", c.Config.Sanitize()).Msg("configuration settings")
|
||||
|
||||
engine := mux.NewRouter()
|
||||
engine.Use(Logger(c.Log))
|
||||
c.Router = engine
|
||||
_ = NewRouteHandler(c)
|
||||
|
||||
c.Log.Info().Interface("params", c.Config).Msg("configuration settings")
|
||||
c.ImageStore = storage.NewImageStore(c.Config.Storage.RootDirectory, c.Log)
|
||||
|
||||
addr := fmt.Sprintf("%s:%s", c.Config.HTTP.Address, c.Config.HTTP.Port)
|
||||
|
@ -45,10 +53,10 @@ func (c *Controller) Run() error {
|
|||
return err
|
||||
}
|
||||
|
||||
if c.Config.HTTP.TLS.Key != "" && c.Config.HTTP.TLS.Cert != "" {
|
||||
if c.Config.HTTP.TLS != nil && c.Config.HTTP.TLS.Key != "" && c.Config.HTTP.TLS.Cert != "" {
|
||||
if c.Config.HTTP.TLS.CACert != "" {
|
||||
clientAuth := tls.VerifyClientCertIfGiven
|
||||
if c.Config.HTTP.Auth.HTPasswd.Path == "" && !c.Config.HTTP.AllowReadAccess {
|
||||
if (c.Config.HTTP.Auth == nil || c.Config.HTTP.Auth.HTPasswd.Path == "") && !c.Config.HTTP.AllowReadAccess {
|
||||
clientAuth = tls.RequireAndVerifyClientCert
|
||||
}
|
||||
|
||||
|
|
|
@ -5,12 +5,16 @@ import (
|
|||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/anuvu/zot/pkg/api"
|
||||
vldap "github.com/nmcclain/ldap"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"gopkg.in/resty.v1"
|
||||
)
|
||||
|
@ -41,7 +45,11 @@ func TestBasicAuth(t *testing.T) {
|
|||
Convey("Make a new controller", t, func() {
|
||||
config := api.NewConfig()
|
||||
config.HTTP.Port = SecurePort1
|
||||
config.HTTP.Auth.HTPasswd.Path = htpasswdPath
|
||||
config.HTTP.Auth = &api.AuthConfig{
|
||||
HTPasswd: api.AuthHTPasswd{
|
||||
Path: htpasswdPath,
|
||||
},
|
||||
}
|
||||
c := api.NewController(config)
|
||||
dir, err := ioutil.TempDir("", "oci-repo-test")
|
||||
if err != nil {
|
||||
|
@ -101,9 +109,15 @@ func TestTLSWithBasicAuth(t *testing.T) {
|
|||
defer func() { resty.SetTLSClientConfig(nil) }()
|
||||
config := api.NewConfig()
|
||||
config.HTTP.Port = SecurePort2
|
||||
config.HTTP.Auth.HTPasswd.Path = htpasswdPath
|
||||
config.HTTP.TLS.Cert = ServerCert
|
||||
config.HTTP.TLS.Key = ServerKey
|
||||
config.HTTP.TLS = &api.TLSConfig{
|
||||
Cert: ServerCert,
|
||||
Key: ServerKey,
|
||||
}
|
||||
config.HTTP.Auth = &api.AuthConfig{
|
||||
HTPasswd: api.AuthHTPasswd{
|
||||
Path: htpasswdPath,
|
||||
},
|
||||
}
|
||||
|
||||
c := api.NewController(config)
|
||||
dir, err := ioutil.TempDir("", "oci-repo-test")
|
||||
|
@ -170,9 +184,15 @@ func TestTLSWithBasicAuthAllowReadAccess(t *testing.T) {
|
|||
defer func() { resty.SetTLSClientConfig(nil) }()
|
||||
config := api.NewConfig()
|
||||
config.HTTP.Port = SecurePort2
|
||||
config.HTTP.Auth.HTPasswd.Path = htpasswdPath
|
||||
config.HTTP.TLS.Cert = ServerCert
|
||||
config.HTTP.TLS.Key = ServerKey
|
||||
config.HTTP.Auth = &api.AuthConfig{
|
||||
HTPasswd: api.AuthHTPasswd{
|
||||
Path: htpasswdPath,
|
||||
},
|
||||
}
|
||||
config.HTTP.TLS = &api.TLSConfig{
|
||||
Cert: ServerCert,
|
||||
Key: ServerKey,
|
||||
}
|
||||
config.HTTP.AllowReadAccess = true
|
||||
|
||||
c := api.NewController(config)
|
||||
|
@ -241,9 +261,11 @@ func TestTLSMutualAuth(t *testing.T) {
|
|||
defer func() { resty.SetTLSClientConfig(nil) }()
|
||||
config := api.NewConfig()
|
||||
config.HTTP.Port = SecurePort2
|
||||
config.HTTP.TLS.Cert = ServerCert
|
||||
config.HTTP.TLS.Key = ServerKey
|
||||
config.HTTP.TLS.CACert = CACert
|
||||
config.HTTP.TLS = &api.TLSConfig{
|
||||
Cert: ServerCert,
|
||||
Key: ServerKey,
|
||||
CACert: CACert,
|
||||
}
|
||||
|
||||
c := api.NewController(config)
|
||||
dir, err := ioutil.TempDir("", "oci-repo-test")
|
||||
|
@ -323,9 +345,11 @@ func TestTLSMutualAuthAllowReadAccess(t *testing.T) {
|
|||
defer func() { resty.SetTLSClientConfig(nil) }()
|
||||
config := api.NewConfig()
|
||||
config.HTTP.Port = SecurePort2
|
||||
config.HTTP.TLS.Cert = ServerCert
|
||||
config.HTTP.TLS.Key = ServerKey
|
||||
config.HTTP.TLS.CACert = CACert
|
||||
config.HTTP.TLS = &api.TLSConfig{
|
||||
Cert: ServerCert,
|
||||
Key: ServerKey,
|
||||
CACert: CACert,
|
||||
}
|
||||
config.HTTP.AllowReadAccess = true
|
||||
|
||||
c := api.NewController(config)
|
||||
|
@ -413,10 +437,16 @@ func TestTLSMutualAndBasicAuth(t *testing.T) {
|
|||
defer func() { resty.SetTLSClientConfig(nil) }()
|
||||
config := api.NewConfig()
|
||||
config.HTTP.Port = SecurePort2
|
||||
config.HTTP.TLS.Cert = ServerCert
|
||||
config.HTTP.TLS.Key = ServerKey
|
||||
config.HTTP.TLS.CACert = CACert
|
||||
config.HTTP.Auth.HTPasswd.Path = htpasswdPath
|
||||
config.HTTP.Auth = &api.AuthConfig{
|
||||
HTPasswd: api.AuthHTPasswd{
|
||||
Path: htpasswdPath,
|
||||
},
|
||||
}
|
||||
config.HTTP.TLS = &api.TLSConfig{
|
||||
Cert: ServerCert,
|
||||
Key: ServerKey,
|
||||
CACert: CACert,
|
||||
}
|
||||
|
||||
c := api.NewController(config)
|
||||
dir, err := ioutil.TempDir("", "oci-repo-test")
|
||||
|
@ -499,10 +529,16 @@ func TestTLSMutualAndBasicAuthAllowReadAccess(t *testing.T) {
|
|||
defer func() { resty.SetTLSClientConfig(nil) }()
|
||||
config := api.NewConfig()
|
||||
config.HTTP.Port = SecurePort2
|
||||
config.HTTP.TLS.Cert = ServerCert
|
||||
config.HTTP.TLS.Key = ServerKey
|
||||
config.HTTP.TLS.CACert = CACert
|
||||
config.HTTP.Auth.HTPasswd.Path = htpasswdPath
|
||||
config.HTTP.Auth = &api.AuthConfig{
|
||||
HTPasswd: api.AuthHTPasswd{
|
||||
Path: htpasswdPath,
|
||||
},
|
||||
}
|
||||
config.HTTP.TLS = &api.TLSConfig{
|
||||
Cert: ServerCert,
|
||||
Key: ServerKey,
|
||||
CACert: CACert,
|
||||
}
|
||||
config.HTTP.AllowReadAccess = true
|
||||
|
||||
c := api.NewController(config)
|
||||
|
@ -578,3 +614,139 @@ func TestTLSMutualAndBasicAuthAllowReadAccess(t *testing.T) {
|
|||
So(resp.StatusCode(), ShouldEqual, 200)
|
||||
})
|
||||
}
|
||||
|
||||
const (
|
||||
LDAPAddress = "127.0.0.1"
|
||||
LDAPPort = 9636
|
||||
LDAPBaseDN = "ou=test"
|
||||
LDAPBindDN = "cn=reader," + LDAPBaseDN
|
||||
LDAPBindPassword = "bindPassword"
|
||||
)
|
||||
|
||||
type testLDAPServer struct {
|
||||
server *vldap.Server
|
||||
quitCh chan bool
|
||||
}
|
||||
|
||||
func newTestLDAPServer() *testLDAPServer {
|
||||
l := &testLDAPServer{}
|
||||
quitCh := make(chan bool)
|
||||
server := vldap.NewServer()
|
||||
server.QuitChannel(quitCh)
|
||||
server.BindFunc("", l)
|
||||
server.SearchFunc("", l)
|
||||
l.server = server
|
||||
l.quitCh = quitCh
|
||||
return l
|
||||
}
|
||||
|
||||
func (l *testLDAPServer) Start() {
|
||||
addr := fmt.Sprintf("%s:%d", LDAPAddress, LDAPPort)
|
||||
go func() {
|
||||
if err := l.server.ListenAndServe(addr); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
for {
|
||||
_, err := net.Dial("tcp", addr)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *testLDAPServer) Stop() {
|
||||
l.quitCh <- true
|
||||
}
|
||||
|
||||
func (l *testLDAPServer) Bind(bindDN, bindSimplePw string, conn net.Conn) (vldap.LDAPResultCode, error) {
|
||||
if bindDN == "" || bindSimplePw == "" {
|
||||
return vldap.LDAPResultInappropriateAuthentication, errors.New("ldap: bind creds required")
|
||||
}
|
||||
if (bindDN == LDAPBindDN && bindSimplePw == LDAPBindPassword) ||
|
||||
(bindDN == fmt.Sprintf("cn=%s,%s", username, LDAPBaseDN) && bindSimplePw == passphrase) {
|
||||
return vldap.LDAPResultSuccess, nil
|
||||
}
|
||||
return vldap.LDAPResultInvalidCredentials, errors.New("ldap: invalid credentials")
|
||||
}
|
||||
|
||||
func (l *testLDAPServer) Search(boundDN string, req vldap.SearchRequest,
|
||||
conn net.Conn) (vldap.ServerSearchResult, error) {
|
||||
check := fmt.Sprintf("(uid=%s)", username)
|
||||
if check == req.Filter {
|
||||
return vldap.ServerSearchResult{
|
||||
Entries: []*vldap.Entry{
|
||||
{DN: fmt.Sprintf("cn=%s,%s", username, LDAPBaseDN)},
|
||||
},
|
||||
ResultCode: vldap.LDAPResultSuccess,
|
||||
}, nil
|
||||
}
|
||||
return vldap.ServerSearchResult{}, nil
|
||||
}
|
||||
|
||||
func TestBasicAuthWithLDAP(t *testing.T) {
|
||||
Convey("Make a new controller", t, func() {
|
||||
l := newTestLDAPServer()
|
||||
l.Start()
|
||||
defer l.Stop()
|
||||
config := api.NewConfig()
|
||||
config.HTTP.Port = SecurePort1
|
||||
config.HTTP.Auth = &api.AuthConfig{
|
||||
LDAP: &api.LDAPConfig{
|
||||
Insecure: true,
|
||||
Address: LDAPAddress,
|
||||
Port: LDAPPort,
|
||||
BindDN: LDAPBindDN,
|
||||
BindPassword: LDAPBindPassword,
|
||||
BaseDN: LDAPBaseDN,
|
||||
UserAttribute: "uid",
|
||||
},
|
||||
}
|
||||
c := api.NewController(config)
|
||||
dir, err := ioutil.TempDir("", "oci-repo-test")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
c.Config.Storage.RootDirectory = dir
|
||||
go func() {
|
||||
// this blocks
|
||||
if err := c.Run(); err != nil {
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
// wait till ready
|
||||
for {
|
||||
_, err := resty.R().Get(BaseURL1)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
ctx := context.Background()
|
||||
_ = c.Server.Shutdown(ctx)
|
||||
}()
|
||||
|
||||
// without creds, should get access error
|
||||
resp, err := resty.R().Get(BaseURL1 + "/v2/")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 401)
|
||||
var e api.Error
|
||||
err = json.Unmarshal(resp.Body(), &e)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// with creds, should get expected status code
|
||||
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(BaseURL1)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 404)
|
||||
|
||||
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(BaseURL1 + "/v2/")
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 200)
|
||||
})
|
||||
}
|
||||
|
|
146
pkg/api/ldap.go
Normal file
146
pkg/api/ldap.go
Normal file
|
@ -0,0 +1,146 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
|
||||
"github.com/anuvu/zot/errors"
|
||||
"github.com/jtblin/go-ldap-client"
|
||||
"github.com/rs/zerolog"
|
||||
goldap "gopkg.in/ldap.v2"
|
||||
)
|
||||
|
||||
type LDAPClient struct {
|
||||
ldap.LDAPClient
|
||||
subtreeSearch bool
|
||||
clientCAs *x509.CertPool
|
||||
log zerolog.Logger
|
||||
}
|
||||
|
||||
// Connect connects to the ldap backend.
|
||||
func (lc *LDAPClient) Connect() error {
|
||||
if lc.Conn == nil {
|
||||
var l *goldap.Conn
|
||||
var err error
|
||||
address := fmt.Sprintf("%s:%d", lc.Host, lc.Port)
|
||||
if !lc.UseSSL {
|
||||
l, err = goldap.Dial("tcp", address)
|
||||
if err != nil {
|
||||
lc.log.Error().Err(err).Str("address", address).Msg("non-TLS connection failed")
|
||||
return err
|
||||
}
|
||||
|
||||
// Reconnect with TLS
|
||||
if !lc.SkipTLS {
|
||||
config := &tls.Config{
|
||||
InsecureSkipVerify: lc.InsecureSkipVerify, // nolint (gosec): InsecureSkipVerify is not true by default
|
||||
RootCAs: lc.clientCAs,
|
||||
}
|
||||
if lc.ClientCertificates != nil && len(lc.ClientCertificates) > 0 {
|
||||
config.Certificates = lc.ClientCertificates
|
||||
config.BuildNameToCertificate()
|
||||
}
|
||||
err = l.StartTLS(config)
|
||||
if err != nil {
|
||||
lc.log.Error().Err(err).Str("address", address).Msg("TLS connection failed")
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
config := &tls.Config{
|
||||
InsecureSkipVerify: lc.InsecureSkipVerify, // nolint (gosec): InsecureSkipVerify is not true by default
|
||||
ServerName: lc.ServerName,
|
||||
RootCAs: lc.clientCAs,
|
||||
}
|
||||
if lc.ClientCertificates != nil && len(lc.ClientCertificates) > 0 {
|
||||
config.Certificates = lc.ClientCertificates
|
||||
config.BuildNameToCertificate()
|
||||
}
|
||||
l, err = goldap.DialTLS("tcp", address, config)
|
||||
if err != nil {
|
||||
lc.log.Error().Err(err).Str("address", address).Msg("TLS connection failed")
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
lc.Conn = l
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Authenticate authenticates the user against the ldap backend.
|
||||
func (lc *LDAPClient) Authenticate(username, password string) (bool, map[string]string, error) {
|
||||
err := lc.Connect()
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
// First bind with a read only user
|
||||
if lc.BindDN != "" && 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")
|
||||
return false, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
attributes := append(lc.Attributes, "dn")
|
||||
searchScope := goldap.ScopeSingleLevel
|
||||
if lc.subtreeSearch {
|
||||
searchScope = goldap.ScopeWholeSubtree
|
||||
}
|
||||
// Search for the given username
|
||||
searchRequest := goldap.NewSearchRequest(
|
||||
lc.Base,
|
||||
searchScope, goldap.NeverDerefAliases, 0, 0, false,
|
||||
fmt.Sprintf(lc.UserFilter, username),
|
||||
attributes,
|
||||
nil,
|
||||
)
|
||||
|
||||
sr, err := lc.Conn.Search(searchRequest)
|
||||
if err != nil {
|
||||
fmt.Printf("%v\n", err)
|
||||
lc.log.Error().Err(err).Str("bindDN", lc.BindDN).Str("username", username).
|
||||
Str("baseDN", lc.Base).Msg("search failed")
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
if len(sr.Entries) < 1 {
|
||||
err := errors.ErrBadUser
|
||||
lc.log.Error().Err(err).Str("bindDN", lc.BindDN).Str("username", username).
|
||||
Str("baseDN", lc.Base).Msg("entries not found")
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
if len(sr.Entries) > 1 {
|
||||
err := errors.ErrEntriesExceeded
|
||||
lc.log.Error().Err(err).Str("bindDN", lc.BindDN).Str("username", username).
|
||||
Str("baseDN", lc.Base).Msg("too many entries")
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
userDN := sr.Entries[0].DN
|
||||
user := map[string]string{}
|
||||
for _, attr := range lc.Attributes {
|
||||
user[attr] = sr.Entries[0].GetAttributeValue(attr)
|
||||
}
|
||||
|
||||
// Bind as the user to verify their password
|
||||
err = lc.Conn.Bind(userDN, password)
|
||||
if err != nil {
|
||||
lc.log.Error().Err(err).Str("bindDN", userDN).Msg("user bind failed")
|
||||
return false, user, err
|
||||
}
|
||||
|
||||
// Rebind as the read only user for any further queries
|
||||
if lc.BindDN != "" && lc.BindPassword != "" {
|
||||
err = lc.Conn.Bind(lc.BindDN, lc.BindPassword)
|
||||
if err != nil {
|
||||
return true, user, err
|
||||
}
|
||||
}
|
||||
|
||||
return true, user, nil
|
||||
}
|
|
@ -21,6 +21,8 @@ go_test(
|
|||
embed = [":go_default_library"],
|
||||
race = "on",
|
||||
deps = [
|
||||
"@com_github_opencontainers_go_digest//:go_default_library",
|
||||
"@com_github_opencontainers_image_spec//specs-go/v1:go_default_library",
|
||||
"@com_github_rs_zerolog//:go_default_library",
|
||||
"@com_github_smartystreets_goconvey//convey:go_default_library",
|
||||
],
|
||||
|
|
Loading…
Reference in a new issue