-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Over the course of writing this documentation, I realized that the current authentication configuration was going to present problems when running in non-combo modes and may even prevent running with more automatic/secure configurations, like mutual TLS. The code changes here are geared toward making those possible while keeping a single-config setup. Signed-off-by: Hank Donnay <hdonnay@redhat.com>
- Loading branch information
Showing
7 changed files
with
282 additions
and
39 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
# Operation | ||
|
||
## Releases | ||
|
||
All of the source code needed to build clair is packaged as an archive and | ||
attached to the release. Releases are tracked at the [github releases]. | ||
|
||
[github releases]: https://github.com/quay/clair/releases | ||
|
||
## Official Containers | ||
|
||
Clair is officially packaged and released as a container at | ||
[quay.io/projectquay/clair]. The `latest` tag tracks the git development branch, | ||
and version tags are built from the corresponding release. | ||
|
||
[quay.io/projectquay/clair]: https://quay.io/repository/projectquay/clair | ||
|
||
## Architecture | ||
|
||
Clair is structured so that it can be easily scaled with demand. It can be | ||
broken up into up to 3 microservices as needed ([Indexer], [Matcher], and | ||
[Notifier]) or run as a single monolith. Each process talks to separate tables | ||
in the database and is responsible for disparate API endpoints. | ||
|
||
[Indexer]: #indexer | ||
[Matcher]: #matcher | ||
[Notifier]: #notifier | ||
|
||
### Indexer | ||
|
||
Responsible for ... | ||
|
||
### Matcher | ||
|
||
Responsible for ... | ||
|
||
### Notifier | ||
|
||
Responsible for ... | ||
|
||
## Ingress | ||
|
||
One recommended configuration is to use some sort of service ingress to route | ||
API endpoints to the component responsible for servicing it. | ||
|
||
## Authentication | ||
|
||
Previous versions of Clair used [jwtproxy] to gate authentication. For ease of | ||
building and deployment, v4 handles authentication itself. | ||
|
||
Authentication is configured by specifying configuration objects underneath the | ||
`auth` key of the configuration. Multiple authentication configurations may be | ||
present, but they will be used preferentially in the order laid out below. | ||
|
||
[jwtproxy]: https://github.com/quay/jwtproxy | ||
|
||
### Quay Integration | ||
|
||
Quay implements a keyserver protocol that allows for publishing and rotating | ||
keys in an automated fashion. Any process that has successfully enrolled in the | ||
keyserver that Clair is configured to talk to should be able to sign requests to | ||
Clair. | ||
|
||
#### Configuration | ||
|
||
The `auth` stanza of the configuration file requires one parameter, `api`, which | ||
is the API endpoint of keyserver protocol. | ||
|
||
```yaml | ||
auth: | ||
keyserver: | ||
api: 'https://quay.example.com/keys/' | ||
``` | ||
|
||
##### Intraservice | ||
|
||
When Clair instances are configured with keyserver authentication and run in any | ||
other mode besides "combo", an additional `intraservice` key is | ||
required. This key is used for signing and verifying requests within the | ||
Clair service cluster. | ||
|
||
```yaml | ||
auth: | ||
keyserver: | ||
api: 'https://quay.example.com/keys/' | ||
intraservice: >- | ||
MDQ4ODBlNDAtNDc0ZC00MWUxLThhMzAtOTk0MzEwMGQwYTMxCg== | ||
``` | ||
|
||
### PSK | ||
|
||
Clair implements JWT-based authentication using a pre-shared key. | ||
|
||
#### Configuration | ||
|
||
The `auth` stanza of the configuration file requires two parameters: `iss`, which | ||
is the issuer to validate on all incoming requests; and `key`, which is a base64 | ||
encoded symmetric key for validating the requests. | ||
|
||
```yaml | ||
auth: | ||
psk: | ||
key: >- | ||
MDQ4ODBlNDAtNDc0ZC00MWUxLThhMzAtOTk0MzEwMGQwYTMxCg== | ||
iss: 'issuer' | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
package config | ||
|
||
import ( | ||
"net/http" | ||
"time" | ||
|
||
"gopkg.in/square/go-jose.v2" | ||
"gopkg.in/square/go-jose.v2/jwt" | ||
) | ||
|
||
// Client returns an http.Client configured according to the supplied | ||
// configuration. | ||
// | ||
// It returns an *http.Client and a boolean indicating whether the client is | ||
// configured for authentication, or an error that occurred during construction. | ||
func (cfg *Config) Client(next *http.Transport) (c *http.Client, authed bool, err error) { | ||
authed = false | ||
sk := jose.SigningKey{Algorithm: jose.HS256} | ||
|
||
// Keep this organized from "best" to "worst". That way, we can add methods | ||
// and keep everything working with some careful cluster rolling. | ||
switch { | ||
case cfg.Auth.Keyserver != nil: | ||
sk.Key = cfg.Auth.Keyserver.Intraservice | ||
case cfg.Auth.PSK != nil: | ||
sk.Key = cfg.Auth.PSK.Key | ||
default: | ||
} | ||
rt := &transport{next: next} | ||
c = &http.Client{Transport: rt} | ||
|
||
// Both of the JWT-based methods set the signing key. | ||
if sk.Key != nil { | ||
signer, err := jose.NewSigner(sk, nil) | ||
if err != nil { | ||
return nil, false, err | ||
} | ||
rt.Signer = signer | ||
authed = true | ||
} | ||
return c, authed, nil | ||
} | ||
|
||
var _ http.RoundTripper = (*transport)(nil) | ||
|
||
// Transport does request modification common to all requests. | ||
type transport struct { | ||
jose.Signer | ||
next http.RoundTripper | ||
} | ||
|
||
func (cs *transport) RoundTrip(r *http.Request) (*http.Response, error) { | ||
const ( | ||
issuer = `clair-intraservice` | ||
userAgent = `clair/v4` | ||
) | ||
r.Header.Set("user-agent", userAgent) | ||
if cs.Signer != nil { | ||
// TODO(hank) Make this mint longer-lived tokens and re-use them, only | ||
// refreshing when needed. Like a resettable sync.Once. | ||
now := time.Now() | ||
cl := jwt.Claims{ | ||
IssuedAt: jwt.NewNumericDate(now), | ||
NotBefore: jwt.NewNumericDate(now.Add(-jwt.DefaultLeeway)), | ||
Expiry: jwt.NewNumericDate(now.Add(jwt.DefaultLeeway)), | ||
Issuer: issuer, | ||
} | ||
h, err := jwt.Signed(cs).Claims(&cl).CompactSerialize() | ||
if err != nil { | ||
return nil, err | ||
} | ||
r.Header.Add("authorization", "Bearer "+h) | ||
} | ||
return cs.next.RoundTrip(r) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.