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 ./...
|
go test -v -race -cover -coverprofile=coverage.txt -covermode=atomic ./...
|
||||||
|
|
||||||
.PHONY: check
|
.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 --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:
|
docs/docs.go:
|
||||||
swag -v || go install github.com/swaggo/swag/cmd/swag
|
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
|
* 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
|
* Uses [OCI storage layout](https://github.com/opencontainers/image-spec/blob/master/image-layout.md) for storage layout
|
||||||
* TLS support
|
* TLS support
|
||||||
* *Basic* and TLS mutual authentication
|
* Authentication via TLS mutual authentication and HTTP *BASIC* (local _htpasswd_ and LDAP)
|
||||||
* Swagger based documentation
|
|
||||||
* Doesn't require _root_ privileges
|
* Doesn't require _root_ privileges
|
||||||
|
* Swagger based documentation
|
||||||
|
|
||||||
# Building
|
# Building
|
||||||
|
|
||||||
|
|
105
WORKSPACE
105
WORKSPACE
|
@ -1008,3 +1008,108 @@ go_repository(
|
||||||
sum = "h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=",
|
sum = "h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=",
|
||||||
version = "v0.0.0-20190717185122-a985d3407aa7",
|
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")
|
ErrBadBlobDigest = errors.New("blob: bad blob digest")
|
||||||
ErrUnknownCode = errors.New("error: unknown error code")
|
ErrUnknownCode = errors.New("error: unknown error code")
|
||||||
ErrBadCACert = errors.New("tls: invalid ca cert")
|
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"
|
"key":"test/data/server.key"
|
||||||
},
|
},
|
||||||
"auth": {
|
"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": {
|
"htpasswd": {
|
||||||
"path": "test/data/htpasswd"
|
"path": "test/data/htpasswd"
|
||||||
},
|
},
|
||||||
|
|
|
@ -11,6 +11,16 @@ http:
|
||||||
cert: test/data/server.cert
|
cert: test/data/server.cert
|
||||||
key: test/data/server.key
|
key: test/data/server.key
|
||||||
auth:
|
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:
|
htpasswd:
|
||||||
path: test/data/htpasswd
|
path: test/data/htpasswd
|
||||||
failDelay: 5
|
failDelay: 5
|
||||||
|
|
6
go.mod
6
go.mod
|
@ -4,13 +4,17 @@ go 1.12
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc
|
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/go-chi/chi v4.0.2+incompatible // indirect
|
||||||
github.com/gofrs/uuid v3.2.0+incompatible
|
github.com/gofrs/uuid v3.2.0+incompatible
|
||||||
github.com/gorilla/mux v1.7.3
|
github.com/gorilla/mux v1.7.3
|
||||||
github.com/json-iterator/go v1.1.6
|
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/mitchellh/mapstructure v1.1.2
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.1 // 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/distribution-spec v1.0.0-rc0
|
||||||
github.com/opencontainers/go-digest v1.0.0-rc1
|
github.com/opencontainers/go-digest v1.0.0-rc1
|
||||||
github.com/opencontainers/image-spec v1.0.1
|
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/sys v0.0.0-20190826190057-c7b8b68b1456 // indirect
|
||||||
golang.org/x/text v0.3.2 // indirect
|
golang.org/x/text v0.3.2 // indirect
|
||||||
golang.org/x/tools v0.0.0-20190827205025-b29f5f60c37a // 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
|
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/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 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
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/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 h1:maB6vn6FqCxrpz4FqWdh4+lwpyZIQS7YEAUcHlgXVRs=
|
||||||
github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
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/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 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs=
|
||||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
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 h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
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=
|
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 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
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/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/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 h1:xMzwhweo1gjvEo74mQjGTLau0TD3ACyTEC1310NbuSQ=
|
||||||
github.com/opencontainers/distribution-spec v1.0.0-rc0/go.mod h1:copR2flp+jTEvQIFMb6MIx45OkrxzqyjszPDT3hx/5Q=
|
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.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
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/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 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 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
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 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI=
|
||||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
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=
|
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||||
|
|
|
@ -7,6 +7,7 @@ go_library(
|
||||||
"config.go",
|
"config.go",
|
||||||
"controller.go",
|
"controller.go",
|
||||||
"errors.go",
|
"errors.go",
|
||||||
|
"ldap.go",
|
||||||
"log.go",
|
"log.go",
|
||||||
"regexp.go",
|
"regexp.go",
|
||||||
"routes.go",
|
"routes.go",
|
||||||
|
@ -17,12 +18,15 @@ go_library(
|
||||||
"//docs:go_default_library",
|
"//docs:go_default_library",
|
||||||
"//errors:go_default_library",
|
"//errors:go_default_library",
|
||||||
"//pkg/storage:go_default_library",
|
"//pkg/storage:go_default_library",
|
||||||
|
"@com_github_getlantern_deepcopy//:go_default_library",
|
||||||
"@com_github_gorilla_mux//:go_default_library",
|
"@com_github_gorilla_mux//:go_default_library",
|
||||||
"@com_github_json_iterator_go//: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_distribution_spec//:go_default_library",
|
||||||
"@com_github_opencontainers_image_spec//specs-go/v1:go_default_library",
|
"@com_github_opencontainers_image_spec//specs-go/v1:go_default_library",
|
||||||
"@com_github_rs_zerolog//:go_default_library",
|
"@com_github_rs_zerolog//:go_default_library",
|
||||||
"@com_github_swaggo_http_swagger//: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",
|
"@org_golang_x_crypto//bcrypt:go_default_library",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@ -40,6 +44,7 @@ go_test(
|
||||||
embed = [":go_default_library"],
|
embed = [":go_default_library"],
|
||||||
race = "on",
|
race = "on",
|
||||||
deps = [
|
deps = [
|
||||||
|
"@com_github_nmcclain_ldap//:go_default_library",
|
||||||
"@com_github_opencontainers_go_digest//:go_default_library",
|
"@com_github_opencontainers_go_digest//:go_default_library",
|
||||||
"@com_github_opencontainers_image_spec//specs-go/v1:go_default_library",
|
"@com_github_opencontainers_image_spec//specs-go/v1:go_default_library",
|
||||||
"@com_github_smartystreets_goconvey//convey:go_default_library",
|
"@com_github_smartystreets_goconvey//convey:go_default_library",
|
||||||
|
|
|
@ -2,14 +2,19 @@ package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"crypto/x509"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/anuvu/zot/errors"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/jtblin/go-ldap-client"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -20,25 +25,25 @@ func authFail(w http.ResponseWriter, realm string, delay int) {
|
||||||
WriteJSON(w, http.StatusUnauthorized, NewError(UNAUTHORIZED))
|
WriteJSON(w, http.StatusUnauthorized, NewError(UNAUTHORIZED))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// nolint (gocyclo) - we use closure making this a complex subroutine
|
||||||
func BasicAuthHandler(c *Controller) mux.MiddlewareFunc {
|
func BasicAuthHandler(c *Controller) mux.MiddlewareFunc {
|
||||||
realm := c.Config.HTTP.Realm
|
realm := c.Config.HTTP.Realm
|
||||||
if realm == "" {
|
if realm == "" {
|
||||||
realm = "Authorization Required"
|
realm = "Authorization Required"
|
||||||
}
|
}
|
||||||
realm = "Basic realm=" + strconv.Quote(realm)
|
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 func(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if c.Config.HTTP.AllowReadAccess &&
|
if c.Config.HTTP.AllowReadAccess &&
|
||||||
c.Config.HTTP.TLS.CACert != "" &&
|
c.Config.HTTP.TLS.CACert != "" &&
|
||||||
r.TLS.VerifiedChains == nil &&
|
r.TLS.VerifiedChains == nil &&
|
||||||
r.Method != "GET" && r.Method != "HEAD" {
|
r.Method != "GET" && r.Method != "HEAD" {
|
||||||
authFail(w, realm, delay)
|
authFail(w, realm, 5)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process request
|
// Process request
|
||||||
next.ServeHTTP(w, r)
|
next.ServeHTTP(w, r)
|
||||||
})
|
})
|
||||||
|
@ -46,20 +51,63 @@ func BasicAuthHandler(c *Controller) mux.MiddlewareFunc {
|
||||||
}
|
}
|
||||||
|
|
||||||
credMap := make(map[string]string)
|
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 c.Config.HTTP.Auth != nil {
|
||||||
if err != nil {
|
if c.Config.HTTP.Auth.LDAP != nil {
|
||||||
panic(err)
|
l := c.Config.HTTP.Auth.LDAP
|
||||||
}
|
ldapClient = &LDAPClient{
|
||||||
|
LDAPClient: ldap.LDAPClient{
|
||||||
for {
|
Host: l.Address,
|
||||||
r := bufio.NewReader(f)
|
Port: l.Port,
|
||||||
line, err := r.ReadString('\n')
|
UseSSL: !l.Insecure,
|
||||||
if err != nil {
|
SkipTLS: !l.StartTLS,
|
||||||
break
|
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 {
|
return func(next http.Handler) http.Handler {
|
||||||
|
@ -97,6 +145,17 @@ func BasicAuthHandler(c *Controller) mux.MiddlewareFunc {
|
||||||
username := pair[0]
|
username := pair[0]
|
||||||
passphrase := pair[1]
|
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]
|
passphraseHash, ok := credMap[username]
|
||||||
if !ok {
|
if !ok {
|
||||||
authFail(w, realm, delay)
|
authFail(w, realm, delay)
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/anuvu/zot/errors"
|
||||||
|
"github.com/getlantern/deepcopy"
|
||||||
dspec "github.com/opencontainers/distribution-spec"
|
dspec "github.com/opencontainers/distribution-spec"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
)
|
)
|
||||||
|
|
||||||
//nolint (gochecknoglobals)
|
//nolint (gochecknoglobals)
|
||||||
|
@ -24,17 +27,32 @@ type AuthHTPasswd struct {
|
||||||
type AuthConfig struct {
|
type AuthConfig struct {
|
||||||
FailDelay int
|
FailDelay int
|
||||||
HTPasswd AuthHTPasswd
|
HTPasswd AuthHTPasswd
|
||||||
|
LDAP *LDAPConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
type HTTPConfig struct {
|
type HTTPConfig struct {
|
||||||
Address string
|
Address string
|
||||||
Port string
|
Port string
|
||||||
TLS TLSConfig `mapstructure:",omitempty"`
|
TLS *TLSConfig
|
||||||
Auth AuthConfig `mapstructure:",omitempty"`
|
Auth *AuthConfig
|
||||||
Realm string
|
Realm string
|
||||||
AllowReadAccess bool `mapstructure:",omitempty"`
|
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 {
|
type LogConfig struct {
|
||||||
Level string
|
Level string
|
||||||
Output string
|
Output string
|
||||||
|
@ -45,7 +63,7 @@ type Config struct {
|
||||||
Commit string
|
Commit string
|
||||||
Storage StorageConfig
|
Storage StorageConfig
|
||||||
HTTP HTTPConfig
|
HTTP HTTPConfig
|
||||||
Log LogConfig `mapstructure:",omitempty"`
|
Log *LogConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConfig() *Config {
|
func NewConfig() *Config {
|
||||||
|
@ -53,6 +71,35 @@ func NewConfig() *Config {
|
||||||
Version: dspec.Version,
|
Version: dspec.Version,
|
||||||
Commit: Commit,
|
Commit: Commit,
|
||||||
HTTP: HTTPConfig{Address: "127.0.0.1", Port: "8080"},
|
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 {
|
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 := mux.NewRouter()
|
||||||
engine.Use(Logger(c.Log))
|
engine.Use(Logger(c.Log))
|
||||||
c.Router = engine
|
c.Router = engine
|
||||||
_ = NewRouteHandler(c)
|
_ = NewRouteHandler(c)
|
||||||
|
|
||||||
c.Log.Info().Interface("params", c.Config).Msg("configuration settings")
|
|
||||||
c.ImageStore = storage.NewImageStore(c.Config.Storage.RootDirectory, c.Log)
|
c.ImageStore = storage.NewImageStore(c.Config.Storage.RootDirectory, c.Log)
|
||||||
|
|
||||||
addr := fmt.Sprintf("%s:%s", c.Config.HTTP.Address, c.Config.HTTP.Port)
|
addr := fmt.Sprintf("%s:%s", c.Config.HTTP.Address, c.Config.HTTP.Port)
|
||||||
|
@ -45,10 +53,10 @@ func (c *Controller) Run() error {
|
||||||
return err
|
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 != "" {
|
if c.Config.HTTP.TLS.CACert != "" {
|
||||||
clientAuth := tls.VerifyClientCertIfGiven
|
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
|
clientAuth = tls.RequireAndVerifyClientCert
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,12 +5,16 @@ import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/anuvu/zot/pkg/api"
|
"github.com/anuvu/zot/pkg/api"
|
||||||
|
vldap "github.com/nmcclain/ldap"
|
||||||
. "github.com/smartystreets/goconvey/convey"
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
"gopkg.in/resty.v1"
|
"gopkg.in/resty.v1"
|
||||||
)
|
)
|
||||||
|
@ -41,7 +45,11 @@ func TestBasicAuth(t *testing.T) {
|
||||||
Convey("Make a new controller", t, func() {
|
Convey("Make a new controller", t, func() {
|
||||||
config := api.NewConfig()
|
config := api.NewConfig()
|
||||||
config.HTTP.Port = SecurePort1
|
config.HTTP.Port = SecurePort1
|
||||||
config.HTTP.Auth.HTPasswd.Path = htpasswdPath
|
config.HTTP.Auth = &api.AuthConfig{
|
||||||
|
HTPasswd: api.AuthHTPasswd{
|
||||||
|
Path: htpasswdPath,
|
||||||
|
},
|
||||||
|
}
|
||||||
c := api.NewController(config)
|
c := api.NewController(config)
|
||||||
dir, err := ioutil.TempDir("", "oci-repo-test")
|
dir, err := ioutil.TempDir("", "oci-repo-test")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -101,9 +109,15 @@ func TestTLSWithBasicAuth(t *testing.T) {
|
||||||
defer func() { resty.SetTLSClientConfig(nil) }()
|
defer func() { resty.SetTLSClientConfig(nil) }()
|
||||||
config := api.NewConfig()
|
config := api.NewConfig()
|
||||||
config.HTTP.Port = SecurePort2
|
config.HTTP.Port = SecurePort2
|
||||||
config.HTTP.Auth.HTPasswd.Path = htpasswdPath
|
config.HTTP.TLS = &api.TLSConfig{
|
||||||
config.HTTP.TLS.Cert = ServerCert
|
Cert: ServerCert,
|
||||||
config.HTTP.TLS.Key = ServerKey
|
Key: ServerKey,
|
||||||
|
}
|
||||||
|
config.HTTP.Auth = &api.AuthConfig{
|
||||||
|
HTPasswd: api.AuthHTPasswd{
|
||||||
|
Path: htpasswdPath,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
c := api.NewController(config)
|
c := api.NewController(config)
|
||||||
dir, err := ioutil.TempDir("", "oci-repo-test")
|
dir, err := ioutil.TempDir("", "oci-repo-test")
|
||||||
|
@ -170,9 +184,15 @@ func TestTLSWithBasicAuthAllowReadAccess(t *testing.T) {
|
||||||
defer func() { resty.SetTLSClientConfig(nil) }()
|
defer func() { resty.SetTLSClientConfig(nil) }()
|
||||||
config := api.NewConfig()
|
config := api.NewConfig()
|
||||||
config.HTTP.Port = SecurePort2
|
config.HTTP.Port = SecurePort2
|
||||||
config.HTTP.Auth.HTPasswd.Path = htpasswdPath
|
config.HTTP.Auth = &api.AuthConfig{
|
||||||
config.HTTP.TLS.Cert = ServerCert
|
HTPasswd: api.AuthHTPasswd{
|
||||||
config.HTTP.TLS.Key = ServerKey
|
Path: htpasswdPath,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
config.HTTP.TLS = &api.TLSConfig{
|
||||||
|
Cert: ServerCert,
|
||||||
|
Key: ServerKey,
|
||||||
|
}
|
||||||
config.HTTP.AllowReadAccess = true
|
config.HTTP.AllowReadAccess = true
|
||||||
|
|
||||||
c := api.NewController(config)
|
c := api.NewController(config)
|
||||||
|
@ -241,9 +261,11 @@ func TestTLSMutualAuth(t *testing.T) {
|
||||||
defer func() { resty.SetTLSClientConfig(nil) }()
|
defer func() { resty.SetTLSClientConfig(nil) }()
|
||||||
config := api.NewConfig()
|
config := api.NewConfig()
|
||||||
config.HTTP.Port = SecurePort2
|
config.HTTP.Port = SecurePort2
|
||||||
config.HTTP.TLS.Cert = ServerCert
|
config.HTTP.TLS = &api.TLSConfig{
|
||||||
config.HTTP.TLS.Key = ServerKey
|
Cert: ServerCert,
|
||||||
config.HTTP.TLS.CACert = CACert
|
Key: ServerKey,
|
||||||
|
CACert: CACert,
|
||||||
|
}
|
||||||
|
|
||||||
c := api.NewController(config)
|
c := api.NewController(config)
|
||||||
dir, err := ioutil.TempDir("", "oci-repo-test")
|
dir, err := ioutil.TempDir("", "oci-repo-test")
|
||||||
|
@ -323,9 +345,11 @@ func TestTLSMutualAuthAllowReadAccess(t *testing.T) {
|
||||||
defer func() { resty.SetTLSClientConfig(nil) }()
|
defer func() { resty.SetTLSClientConfig(nil) }()
|
||||||
config := api.NewConfig()
|
config := api.NewConfig()
|
||||||
config.HTTP.Port = SecurePort2
|
config.HTTP.Port = SecurePort2
|
||||||
config.HTTP.TLS.Cert = ServerCert
|
config.HTTP.TLS = &api.TLSConfig{
|
||||||
config.HTTP.TLS.Key = ServerKey
|
Cert: ServerCert,
|
||||||
config.HTTP.TLS.CACert = CACert
|
Key: ServerKey,
|
||||||
|
CACert: CACert,
|
||||||
|
}
|
||||||
config.HTTP.AllowReadAccess = true
|
config.HTTP.AllowReadAccess = true
|
||||||
|
|
||||||
c := api.NewController(config)
|
c := api.NewController(config)
|
||||||
|
@ -413,10 +437,16 @@ func TestTLSMutualAndBasicAuth(t *testing.T) {
|
||||||
defer func() { resty.SetTLSClientConfig(nil) }()
|
defer func() { resty.SetTLSClientConfig(nil) }()
|
||||||
config := api.NewConfig()
|
config := api.NewConfig()
|
||||||
config.HTTP.Port = SecurePort2
|
config.HTTP.Port = SecurePort2
|
||||||
config.HTTP.TLS.Cert = ServerCert
|
config.HTTP.Auth = &api.AuthConfig{
|
||||||
config.HTTP.TLS.Key = ServerKey
|
HTPasswd: api.AuthHTPasswd{
|
||||||
config.HTTP.TLS.CACert = CACert
|
Path: htpasswdPath,
|
||||||
config.HTTP.Auth.HTPasswd.Path = htpasswdPath
|
},
|
||||||
|
}
|
||||||
|
config.HTTP.TLS = &api.TLSConfig{
|
||||||
|
Cert: ServerCert,
|
||||||
|
Key: ServerKey,
|
||||||
|
CACert: CACert,
|
||||||
|
}
|
||||||
|
|
||||||
c := api.NewController(config)
|
c := api.NewController(config)
|
||||||
dir, err := ioutil.TempDir("", "oci-repo-test")
|
dir, err := ioutil.TempDir("", "oci-repo-test")
|
||||||
|
@ -499,10 +529,16 @@ func TestTLSMutualAndBasicAuthAllowReadAccess(t *testing.T) {
|
||||||
defer func() { resty.SetTLSClientConfig(nil) }()
|
defer func() { resty.SetTLSClientConfig(nil) }()
|
||||||
config := api.NewConfig()
|
config := api.NewConfig()
|
||||||
config.HTTP.Port = SecurePort2
|
config.HTTP.Port = SecurePort2
|
||||||
config.HTTP.TLS.Cert = ServerCert
|
config.HTTP.Auth = &api.AuthConfig{
|
||||||
config.HTTP.TLS.Key = ServerKey
|
HTPasswd: api.AuthHTPasswd{
|
||||||
config.HTTP.TLS.CACert = CACert
|
Path: htpasswdPath,
|
||||||
config.HTTP.Auth.HTPasswd.Path = htpasswdPath
|
},
|
||||||
|
}
|
||||||
|
config.HTTP.TLS = &api.TLSConfig{
|
||||||
|
Cert: ServerCert,
|
||||||
|
Key: ServerKey,
|
||||||
|
CACert: CACert,
|
||||||
|
}
|
||||||
config.HTTP.AllowReadAccess = true
|
config.HTTP.AllowReadAccess = true
|
||||||
|
|
||||||
c := api.NewController(config)
|
c := api.NewController(config)
|
||||||
|
@ -578,3 +614,139 @@ func TestTLSMutualAndBasicAuthAllowReadAccess(t *testing.T) {
|
||||||
So(resp.StatusCode(), ShouldEqual, 200)
|
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"],
|
embed = [":go_default_library"],
|
||||||
race = "on",
|
race = "on",
|
||||||
deps = [
|
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_rs_zerolog//:go_default_library",
|
||||||
"@com_github_smartystreets_goconvey//convey:go_default_library",
|
"@com_github_smartystreets_goconvey//convey:go_default_library",
|
||||||
],
|
],
|
||||||
|
|
Loading…
Reference in a new issue