Skip to content

Commit

Permalink
refactor(extensions)!: refactor the extensions URLs and errors (#1636)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: The functionality provided by the mgmt endpoint has beed redesigned - see details below
BREAKING CHANGE: The API keys endpoint has been moved -  see details below
BREAKING CHANGE: The mgmt extension config has been removed - endpoint is now enabled by having both the search and the ui extensions enabled
BREAKING CHANGE: The API keys configuration has been moved from extensions to http>auth>apikey

mgmt and imagetrust extensions:
- separate the _zot/ext/mgmt into 3 separate endpoints: _zot/ext/auth, _zot/ext/notation, _zot/ext/cosign
- signature verification logic is in a separate `imagetrust` extension
- better hanling or errors in case of signature uploads: logging and error codes (more 400 and less 500 errors)
- add authz on signature uploads (and add a new middleware in common for this purpose)
- remove the mgmt extension configuration - it is now enabled if the UI and the search extensions are enabled

userprefs estension:
- userprefs are enabled if both search and ui extensions are enabled (as opposed to just search)

apikey extension is removed and logic moved into the api folder
- Move apikeys code out of pkg/extensions and into pkg/api
- Remove apikey configuration options from the extensions configuration and move it inside the http auth section
- remove the build label apikeys

other changes:
- move most of the logic adding handlers to the extensions endpoints out of routes.go and into the extensions files.
- add warnings in case the users are still using configurations with the obsolete settings for mgmt and api keys
- add a new function in the extension package which could be a single point of starting backgroud tasks for all extensions
- more clear methods for verifying specific extensions are enabled
- fix http methods paired with the UI handlers
- rebuild swagger docs

Signed-off-by: Andrei Aaron <[email protected]>
  • Loading branch information
andaaron authored Aug 2, 2023
1 parent 42f9f78 commit 77149aa
Show file tree
Hide file tree
Showing 61 changed files with 3,356 additions and 1,422 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
env:
CGO_ENABLED: 0
GOFLAGS: "-tags=sync,search,scrub,metrics,userprefs,apikey,containers_image_openpgp"
GOFLAGS: "-tags=sync,search,scrub,metrics,userprefs,mgmt,imagetrust,containers_image_openpgp"

steps:
- name: Checkout repository
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/golangci-lint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:

# Optional: golangci-lint command line arguments.
# args: --issues-exit-code=0
args: --config ./golangcilint.yaml --enable-all --build-tags debug,needprivileges,sync,scrub,search,userprefs,metrics,containers_image_openpgp,lint,mgmt,apikey ./cmd/... ./pkg/...
args: --config ./golangcilint.yaml --enable-all --build-tags debug,needprivileges,sync,scrub,search,userprefs,metrics,containers_image_openpgp,lint,mgmt,imagetrust ./cmd/... ./pkg/...

# Optional: show only new issues if it's a pull request. The default value is `false`.
# only-new-issues: true
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,5 @@ coverage.html
tags
vendor/
.vscode/
examples/config-sync-localhost.json
examples/config-sync-localhost.json
node_modules
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ TESTDATA := $(TOP_LEVEL)/test/data
OS ?= linux
ARCH ?= amd64
BENCH_OUTPUT ?= stdout
EXTENSIONS ?= sync,search,scrub,metrics,lint,ui,mgmt,userprefs,apikey
UI_DEPENDENCIES := search,mgmt,userprefs,apikey
EXTENSIONS ?= sync,search,scrub,metrics,lint,ui,mgmt,userprefs,imagetrust
UI_DEPENDENCIES := search,mgmt,userprefs
# freebsd/arm64 not supported for pie builds
BUILDMODE_FLAGS := -buildmode=pie
ifeq ($(OS),freebsd)
Expand Down
1 change: 1 addition & 0 deletions errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ var (
ErrInvalidTruststoreType = errors.New("signatures: invalid truststore type")
ErrInvalidTruststoreName = errors.New("signatures: invalid truststore name")
ErrInvalidCertificateContent = errors.New("signatures: invalid certificate content")
ErrInvalidPublicKeyContent = errors.New("signatures: invalid public key content")
ErrInvalidStateCookie = errors.New("auth: state cookie not present or differs from original state")
ErrSyncNoURLsLeft = errors.New("sync: no valid registry urls left after filtering local ones")
)
104 changes: 89 additions & 15 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,20 +207,20 @@ zot can be configured to use the above providers with:
}
```

the login with either provider use http://127.0.0.1:8080/auth/login?provider=\<provider\>&callback_ui=http://127.0.0.1:8080/home
The login with either provider use http://127.0.0.1:8080/auth/login?provider=\<provider\>&callback_ui=http://127.0.0.1:8080/home
for example to login with github use http://127.0.0.1:8080/auth/login?provider=github&callback_ui=http://127.0.0.1:8080/home

callback_ui query parameter is used by zot to redirect to UI after a successful openid/oauth2 authentication

the callback url which should be used when making oauth2 provider setup is http://127.0.0.1:8080/auth/callback/\<provider\>
The callback url which should be used when making oauth2 provider setup is http://127.0.0.1:8080/auth/callback/\<provider\>
for example github callback url would be http://127.0.0.1:8080/auth/callback/github

If network policy doesn't allow inbound connections, this callback wont work!

dex is an identity service that uses OpenID Connect to drive authentication for other apps https://github.com/dexidp/dex
To setup dex service see https://dexidp.io/docs/getting-started/

to configure zot as a client in dex (assuming zot is hosted at 127.0.0.1:8080), we need to configure dex with:
To configure zot as a client in dex (assuming zot is hosted at 127.0.0.1:8080), we need to configure dex with:

```
staticClients:
Expand Down Expand Up @@ -251,7 +251,12 @@ zot can be configured to use dex with:
}
```

to login using openid dex provider use http://127.0.0.1:8080/auth/login?provider=dex
To login using openid dex provider use http://127.0.0.1:8080/auth/login?provider=dex

NOTE: Social login is not supported by command line tools, or other software responsible for pushing/pulling
images to/from zot.
Given this limitation, if openif authentication is enabled in the configuration, API keys are also enabled
implicitly, as a viable alternative authentication method for pushing and pulling container images.

### Session based login

Expand All @@ -261,31 +266,100 @@ Using that cookie on subsequent calls will authenticate them, asumming the cooki
In case of using filesystem storage sessions are saved in zot's root directory.
In case of using cloud storage sessions are saved in memory.

#### Authentication Failures
#### API keys

Should authentication fail, to prevent automated attacks, a delayed response can be configured with:
zot allows authentication for REST API calls using your API key as an alternative to your password.
The user can create or revoke his API keys after he has already authenticated using a different authentication mechanism.
An API key is shown to the user only when it is created. It can not be retrieved from zot with any other call.
An API key has the same permissions as the user who generated it.

Below are several use cases where API keys offer advantages:

- OpenID/OAuth2 social login is not supported by command-line tools or other such clients. In this case, the user
can login to zot using OpenID/OAuth2 and generate API keys to use later when pushing and pulling images.
- In cases where LDAP authentication is used and the user has scripts pushing or pulling images, he will probably not
want to store his LDAP username and password in a shared environment where there is a chance they are compromised.
If he generates and uses an API key instead, the security impact of that key being compromised is limited to zot,
the other services he accesses based on LDAP would not be affected.

To activate API keys use:

```
"http": {
"auth": {
"failDelay": 5
"apikey: true
```

#### API keys
##### How to create an API Key

zot allows authentication for REST API calls using your API key as an alternative to your password.
for more info see [API keys doc](../pkg/extensions/README_apikey.md)
Create an API key for the current user using the REST API

To activate API keys use:
**Usage**: POST /auth/apikey

**Produces**: application/json

**Sample input**:

```
"extensions": {
"apikey": {
"enable": true
}
POST /auth/apiKey
Body: {"label": "git", "scopes": ["repo1", "repo2"]}'
```

**Example cURL**

```bash
curl -u user:password -X POST http://localhost:8080/auth/apikey -d '{"label": "myLabel"}'
```

**Sample output**:

```json
{
"createdAt": "2023-05-05T15:39:28.420926+03:00",
"creatorUa": "curl/7.68.0",
"generatedBy": "manual",
"lastUsed": "2023-05-05T15:39:28.4209282+03:00",
"label": "git",
"scopes": null,
"uuid": "46a45ce7-5d92-498a-a9cb-9654b1da3da1",
"apiKey": "zak_e77bcb9e9f634f1581756abbf9ecd269"
}
```

##### How to use API Keys

**Using API keys with cURL**

```bash
curl -u user:zak_e77bcb9e9f634f1581756abbf9ecd269 http://localhost:8080/v2/_catalog
```

Other command line tools will similarly accept the API key instead of a password.

##### How to revoke an API Key

How to revoke an API key for the current user

**Usage**: DELETE /auth/apiKey?id=$uuid

**Produces**: application/json

**Example cURL**

```bash
curl -u user:password -X DELETE http://localhost:8080/v2/auth/apikey?id=46a45ce7-5d92-498a-a9cb-9654b1da3da1
```

#### Authentication Failures

Should authentication fail, to prevent automated attacks, a delayed response can be configured with:

```
"http": {
"auth": {
"failDelay": 5
```

## Identity-based Authorization

Allowing actions on one or more repository paths can be tied to user
Expand Down
3 changes: 0 additions & 3 deletions examples/config-allextensions.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,6 @@
"scrub": {
"enable": true,
"interval": "24h"
},
"mgmt": {
"enable": true
}
}
}
10 changes: 2 additions & 8 deletions examples/config-openid.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"htpasswd": {
"path": "test/data/htpasswd"
},
"apikey": true,
"openid": {
"providers": {
"github": {
Expand Down Expand Up @@ -64,12 +65,5 @@
"log": {
"level": "debug"
},
"extensions": {
"apikey": {
"enable": true
},
"mgmt": {
"enable": true
}
}
"extensions": {}
}
3 changes: 0 additions & 3 deletions examples/config-ui.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,6 @@
},
"ui": {
"enable": true
},
"mgmt": {
"enable": true
}
}
}
27 changes: 25 additions & 2 deletions pkg/api/authn.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"time"

"github.com/chartmuseum/auth"
guuid "github.com/gofrs/uuid"
"github.com/google/go-github/v52/github"
"github.com/google/uuid"
"github.com/gorilla/mux"
Expand Down Expand Up @@ -353,7 +354,7 @@ func (amw *AuthnMiddleware) TryAuthnHandlers(ctlr *Controller) mux.MiddlewareFun
return
}

isMgmtRequested := request.RequestURI == constants.FullMgmtPrefix
isMgmtRequested := request.RequestURI == constants.FullMgmt
allowAnonymous := ctlr.Config.HTTP.AccessControl.AnonymousPolicyExists()

// try basic auth if authorization header is given
Expand Down Expand Up @@ -443,7 +444,7 @@ func bearerAuthHandler(ctlr *Controller) mux.MiddlewareFunc {
name := vars["name"]

// we want to bypass auth for mgmt route
isMgmtRequested := request.RequestURI == constants.FullMgmtPrefix
isMgmtRequested := request.RequestURI == constants.FullMgmt

header := request.Header.Get("Authorization")

Expand Down Expand Up @@ -849,3 +850,25 @@ func GetAuthUserFromRequestSession(cookieStore sessions.Store, request *http.Req

return identity, true
}

func GenerateAPIKey(uuidGenerator guuid.Generator, log log.Logger,
) (string, string, error) {
apiKeyBase, err := uuidGenerator.NewV4()
if err != nil {
log.Error().Err(err).Msg("unable to generate uuid for api key base")

return "", "", err
}

apiKey := strings.ReplaceAll(apiKeyBase.String(), "-", "")

// will be used for identifying a specific api key
apiKeyID, err := uuidGenerator.NewV4()
if err != nil {
log.Error().Err(err).Msg("unable to generate uuid for api key id")

return "", "", err
}

return apiKey, apiKeyID.String(), err
}
Loading

0 comments on commit 77149aa

Please sign in to comment.