Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Get username in case of certificates authentication #17

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ecosystem-tools.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,6 @@ jobs:
- name: Run scrub tests
run: |
make bats-scrub
- name: Run anonymous-push-pull tests
run: |
make anonymous-push-pull
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -315,3 +315,7 @@ fuzz-all:
rm -rf test-data; \
bash test/scripts/fuzzAll.sh ${fuzztime}; \
rm -rf pkg/storage/testdata; \

.PHONY: anonymous-push-pull
anonymous-push-pull: binary check-skopeo $(BATS)
$(BATS) --trace --print-output-on-failure test/blackbox/anonymous_policiy.bats
1 change: 1 addition & 0 deletions errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,5 @@ var (
ErrSyncSignatureNotFound = errors.New("sync: couldn't find any upstream notary/cosign signatures")
ErrSyncSignature = errors.New("sync: couldn't get upstream notary/cosign signatures")
ErrImageLintAnnotations = errors.New("routes: lint checks failed")
ErrParsingAuthHeader = errors.New("auth: failed parsing authorization header")
)
10 changes: 2 additions & 8 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,6 @@ Additionally, TLS configuration can be specified with:
},
```

The registry can be deployed as a read-only service with:

```
"ReadOnly": false
},
```

## Storage

Configure storage with:
Expand Down Expand Up @@ -209,7 +202,8 @@ create/update/delete can not be used without 'read' action, make sure read is al
"actions": ["read", "create", "update"]
}
],
"defaultPolicy": ["read", "create"] # default policy which is applied for all users => so all users can read/create repositories
"defaultPolicy": ["read", "create"], # default policy which is applied for authenticated users, other than "charlie"=> so these users can read/create repositories
"anonymousPolicy": ["read] # anonymous policy which is applied for unauthenticated users => so they can read repositories
},
"tmp/**": { # matches all repos under tmp/ recursively
"defaultPolicy": ["read", "create", "update"] # so all users have read/create/update on all repos under tmp/ eg: tmp/infra/repo
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,25 @@
"realm": "zot",
"accessControl": {
"**": {
"defaultPolicy": [
"anonymousPolicy": [
"read",
"create"
]
},
"tmp/**": {
"defaultPolicy": [
"anonymousPolicy": [
"read",
"create",
"update"
]
},
"infra/**": {
"defaultPolicy": [
"anonymousPolicy": [
"read"
]
},
"repos2/repo": {
"defaultPolicy": [
"anonymousPolicy": [
"read"
]
}
Expand Down
3 changes: 1 addition & 2 deletions examples/config-bench.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
},
"http": {
"address": "127.0.0.1",
"port": "8080",
"ReadOnly": false
"port": "8080"
},
"log": {
"level": "debug",
Expand Down
3 changes: 1 addition & 2 deletions examples/config-commit.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
},
"http": {
"address": "127.0.0.1",
"port": "8080",
"ReadOnly": false
"port": "8080"
},
"log": {
"level": "debug"
Expand Down
3 changes: 1 addition & 2 deletions examples/config-example.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@
"path": "test/data/htpasswd"
},
"failDelay": 5
},
"allowReadAccess": false
}
},
"log": {
"level": "debug",
Expand Down
1 change: 0 additions & 1 deletion examples/config-example.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
distspecversion: 1.0.1-dev
http:
address: 127.0.0.1
allowreadaccess: false
auth:
faildelay: 5
htpasswd:
Expand Down
3 changes: 1 addition & 2 deletions examples/config-gc.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
},
"http": {
"address": "127.0.0.1",
"port": "8080",
"ReadOnly": false
"port": "8080"
},
"log": {
"level": "debug"
Expand Down
5 changes: 2 additions & 3 deletions examples/config-lint.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
},
"http": {
"address": "127.0.0.1",
"port": "8080",
"ReadOnly": false
"port": "8080"
},
"log": {
"level": "debug"
Expand All @@ -18,4 +17,4 @@

}
}
}
}
3 changes: 1 addition & 2 deletions examples/config-minimal.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
},
"http": {
"address": "127.0.0.1",
"port": "8080",
"ReadOnly": false
"port": "8080"
},
"log": {
"level": "debug"
Expand Down
3 changes: 1 addition & 2 deletions examples/config-multiple-cve.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@
},
"http": {
"address": "127.0.0.1",
"port": "5000",
"ReadOnly": false
"port": "5000"
},
"log": {
"level": "debug"
Expand Down
3 changes: 1 addition & 2 deletions examples/config-multiple.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@
},
"http": {
"address": "127.0.0.1",
"port": "5000",
"ReadOnly": false
"port": "5000"
},
"log": {
"level": "debug"
Expand Down
1 change: 1 addition & 0 deletions examples/config-policy.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
},
"accessControl": {
"**": {
"anonymousPolicy": ["read"],
"policies": [
{
"users": [
Expand Down
1 change: 0 additions & 1 deletion examples/config-ratelimit.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
"http": {
"address": "127.0.0.1",
"port": "8080",
"ReadOnly": false,
"Ratelimit": {
"Rate": 10,
"Methods": [
Expand Down
3 changes: 1 addition & 2 deletions examples/config-s3.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,7 @@
},
"http": {
"address": "127.0.0.1",
"port": "8080",
"ReadOnly": false
"port": "8080"
},
"log": {
"level": "debug"
Expand Down
9 changes: 8 additions & 1 deletion examples/config-tls.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,14 @@
"realm": "zot",
"tls": {
"cert": "test/data/server.cert",
"key": "test/data/server.key"
"key": "test/data/server.key",
"cacert": "test/data/ca.crt"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove this changes, no need for them.

Copy link
Owner Author

@nicoldr nicoldr Aug 2, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will remove them when making the PR, thank you!

},
"accessControl": {
"**": {
"anonymousPolicy": ["read"],
"defaultPolicy": ["read"]
}
}
},
"log": {
Expand Down
87 changes: 42 additions & 45 deletions pkg/api/authn.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,22 +86,6 @@ func noPasswdAuth(realm string, config *config.Config) mux.MiddlewareFunc {
return
}

if config.HTTP.AllowReadAccess &&
config.HTTP.TLS.CACert != "" &&
request.TLS.VerifiedChains == nil &&
request.Method != http.MethodGet && request.Method != http.MethodHead {
authFail(response, realm, 5) //nolint:gomnd

return
}

if (request.Method != http.MethodGet && request.Method != http.MethodHead) && config.HTTP.ReadOnly {
// Reject modification requests in read-only mode
response.WriteHeader(http.StatusMethodNotAllowed)

return
}

// Process request
next.ServeHTTP(response, request)
})
Expand Down Expand Up @@ -197,52 +181,37 @@ func basicAuthHandler(ctlr *Controller) mux.MiddlewareFunc {

return
}
if (request.Method == http.MethodGet || request.Method == http.MethodHead) && ctlr.Config.HTTP.AllowReadAccess {
if request.Header.Get("Authorization") == "" && checkAnonymousPolicyExists(ctlr.Config.AccessControl) {
// Process request
next.ServeHTTP(response, request)

return
}

if (request.Method != http.MethodGet && request.Method != http.MethodHead) && ctlr.Config.HTTP.ReadOnly {
response.WriteHeader(http.StatusMethodNotAllowed)

return
}

basicAuth := request.Header.Get("Authorization")
if basicAuth == "" {
authFail(response, realm, delay)

return
}

splitStr := strings.SplitN(basicAuth, " ", 2) //nolint:gomnd

if len(splitStr) != 2 || strings.ToLower(splitStr[0]) != "basic" {
authFail(response, realm, delay)
username, passphrase, err := getUsernamePasswordBasicAuth(request)
if err != nil {
if request.TLS.VerifiedChains != nil && checkAnonymousPolicyExists(ctlr.Config.AccessControl) {
// Process request
next.ServeHTTP(response, request)

return
}
return
}

decodedStr, err := base64.StdEncoding.DecodeString(splitStr[1])
if err != nil {
ctlr.Log.Error().Err(err).Msg("failed to parse authorization header")
authFail(response, realm, delay)

return
}

pair := strings.SplitN(string(decodedStr), ":", 2) //nolint:gomnd
// nolint:gomnd
if len(pair) != 2 {
authFail(response, realm, delay)
// some client tools might send Authorization: Basic Og== (decoded into ":")
// empty username and password
if username == "" && passphrase == "" && checkAnonymousPolicyExists(ctlr.Config.AccessControl) {
// Process request
next.ServeHTTP(response, request)

return
}

username := pair[0]
passphrase := pair[1]

// first, HTTPPassword authN (which is local)
passphraseHash, ok := credMap[username]
if ok {
Expand Down Expand Up @@ -297,3 +266,31 @@ func authFail(w http.ResponseWriter, realm string, delay int) {
w.Header().Set("Content-Type", "application/json")
WriteJSON(w, http.StatusUnauthorized, NewErrorList(NewError(UNAUTHORIZED)))
}

func getUsernamePasswordBasicAuth(request *http.Request) (string, string, error) {
basicAuth := request.Header.Get("Authorization")

if basicAuth == "" {
return "", "", errors.ErrParsingAuthHeader
}

splitStr := strings.SplitN(basicAuth, " ", 2) //nolint:gomnd
if len(splitStr) != 2 || strings.ToLower(splitStr[0]) != "basic" {
return "", "", errors.ErrParsingAuthHeader
}

decodedStr, err := base64.StdEncoding.DecodeString(splitStr[1])
if err != nil {
return "", "", err
}

pair := strings.SplitN(string(decodedStr), ":", 2) //nolint:gomnd
if len(pair) != 2 { //nolint:gomnd
return "", "", errors.ErrParsingAuthHeader
}

username := pair[0]
passphrase := pair[1]

return username, passphrase, nil
}
Loading