Skip to content

Commit

Permalink
api: implement fernet encryption of pagination tokens
Browse files Browse the repository at this point in the history
  • Loading branch information
jzelinskie committed Feb 24, 2016
1 parent b8c534c commit d19a434
Show file tree
Hide file tree
Showing 16 changed files with 520 additions and 23 deletions.
4 changes: 4 additions & 0 deletions Godeps/Godeps.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion api/context/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/julienschmidt/httprouter"
"github.com/prometheus/client_golang/prometheus"

"github.com/coreos/clair/config"
"github.com/coreos/clair/database"
"github.com/coreos/clair/utils"
)
Expand Down Expand Up @@ -58,5 +59,6 @@ func HTTPHandler(handler Handler, ctx *RouteContext) httprouter.Handle {
}

type RouteContext struct {
Store database.Datastore
Store database.Datastore
Config *config.APIConfig
}
46 changes: 34 additions & 12 deletions api/v1/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,20 @@
package v1

import (
"bytes"
"encoding/json"
"errors"
"fmt"
"time"

"github.com/coreos/clair/database"
"github.com/coreos/clair/utils/types"
"github.com/coreos/pkg/capnslog"
"github.com/fernet/fernet-go"
)

var log = capnslog.NewPackageLogger("github.com/coreos/clair", "v1")

type Error struct {
Message string `json:"Layer`
}
Expand Down Expand Up @@ -187,10 +194,9 @@ type Notification struct {
NextPage string `json:"NextPage,omitempty"`
Old *VulnerabilityWithLayers `json:"Old,omitempty"`
New *VulnerabilityWithLayers `json:"New,omitempty"`
Changed []string `json:"Changed,omitempty"`
}

func NotificationFromDatabaseModel(dbNotification database.VulnerabilityNotification, limit int, page, nextPage database.VulnerabilityNotificationPageNumber) Notification {
func NotificationFromDatabaseModel(dbNotification database.VulnerabilityNotification, limit int, page, nextPage database.VulnerabilityNotificationPageNumber, key string) Notification {
var oldVuln *VulnerabilityWithLayers
if dbNotification.OldVulnerability != nil {
v := VulnerabilityWithLayersFromDatabaseModel(*dbNotification.OldVulnerability)
Expand All @@ -205,7 +211,7 @@ func NotificationFromDatabaseModel(dbNotification database.VulnerabilityNotifica

var nextPageStr string
if nextPage != database.NoVulnerabilityNotificationPage {
nextPageStr = DBPageNumberToString(nextPage)
nextPageStr = pageNumberToToken(nextPage, key)
}

var created, notified, deleted string
Expand All @@ -227,7 +233,7 @@ func NotificationFromDatabaseModel(dbNotification database.VulnerabilityNotifica
Notified: notified,
Deleted: deleted,
Limit: limit,
Page: DBPageNumberToString(page),
Page: pageNumberToToken(page, key),
NextPage: nextPageStr,
Old: oldVuln,
New: newVuln,
Expand Down Expand Up @@ -279,14 +285,30 @@ type FeatureEnvelope struct {
Error *Error `json:"Error,omitempty"`
}

func pageStringToDBPageNumber(pageStr string) (database.VulnerabilityNotificationPageNumber, error) {
// TODO(jzelinskie): turn pagination into an encrypted token
var old, new int
_, err := fmt.Sscanf(pageStr, "%d-%d", &old, &new)
return database.VulnerabilityNotificationPageNumber{old, new}, err
func tokenToPageNumber(token, key string) (database.VulnerabilityNotificationPageNumber, error) {
k, _ := fernet.DecodeKey(key)
msg := fernet.VerifyAndDecrypt([]byte(token), time.Hour, []*fernet.Key{k})
if msg == nil {
return database.VulnerabilityNotificationPageNumber{}, errors.New("invalid or expired pagination token")
}

page := database.VulnerabilityNotificationPageNumber{}
err := json.NewDecoder(bytes.NewBuffer(msg)).Decode(&page)
return page, err
}

func DBPageNumberToString(page database.VulnerabilityNotificationPageNumber) string {
// TODO(jzelinskie): turn pagination into an encrypted token
return fmt.Sprintf("%d-%d", page.OldVulnerability, page.NewVulnerability)
func pageNumberToToken(page database.VulnerabilityNotificationPageNumber, key string) string {
var buf bytes.Buffer
err := json.NewEncoder(&buf).Encode(page)
if err != nil {
log.Fatal("failed to encode VulnerabilityNotificationPageNumber")
}

k, _ := fernet.DecodeKey(key)
tokenBytes, err := fernet.EncryptAndSign(buf.Bytes(), k)
if err != nil {
log.Fatal("failed to encrypt VulnerabilityNotificationpageNumber")
}

return string(tokenBytes)
}
4 changes: 2 additions & 2 deletions api/v1/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ func getNotification(w http.ResponseWriter, r *http.Request, p httprouter.Params
page := database.VulnerabilityNotificationFirstPage
pageStrs, pageExists := query["page"]
if pageExists {
page, err = pageStringToDBPageNumber(pageStrs[0])
page, err = tokenToPageNumber(pageStrs[0], ctx.Config.PaginationKey)
if err != nil {
writeResponse(w, http.StatusBadRequest, NotificationEnvelope{Error: &Error{"invalid page format: " + err.Error()}})
return getNotificationRoute, http.StatusBadRequest
Expand All @@ -337,7 +337,7 @@ func getNotification(w http.ResponseWriter, r *http.Request, p httprouter.Params
return getNotificationRoute, http.StatusInternalServerError
}

notification := NotificationFromDatabaseModel(dbNotification, limit, page, nextPage)
notification := NotificationFromDatabaseModel(dbNotification, limit, page, nextPage, ctx.Config.PaginationKey)

writeResponse(w, http.StatusOK, NotificationEnvelope{Notification: &notification})
return getNotificationRoute, http.StatusOK
Expand Down
4 changes: 2 additions & 2 deletions clair.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ func Boot(config *config.Config) {

// Start API
st.Begin()
go api.Run(config.API, &context.RouteContext{db}, st)
go api.Run(config.API, &context.RouteContext{db, config.API}, st)
st.Begin()
go api.RunHealth(config.API, &context.RouteContext{db}, st)
go api.RunHealth(config.API, &context.RouteContext{db, config.API}, st)

// Start updater
st.Begin()
Expand Down
4 changes: 3 additions & 1 deletion config.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
database:
# PostgreSQL Connection string.
# Reference: http://www.postgresql.org/docs/9.4/static/libpq-connect.html
source:
source:
# Number of elements to keep in the cache.
cacheSize: 16384
api:
Expand All @@ -13,6 +13,8 @@ api:
healthport: 6061
# Maximum time that API requests may take before they time-out with a HTTP 503 error.
timeout: 900s
# 32-bit key used to encrypt pagination tokens
paginationKey: "2E9IgrgWLNb4gjuU0WbiBIudLH8xolz_qxFn--vxJP8="
# Paths to certificates to secure the main API with TLS and client certificate auth.
cafile:
keyfile:
Expand Down
29 changes: 24 additions & 5 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"os"
"time"

"github.com/fernet/fernet-go"
"gopkg.in/yaml.v2"
)

Expand All @@ -44,16 +45,17 @@ type UpdaterConfig struct {

// NotifierConfig is the configuration for the Notifier service and its registered notifiers.
type NotifierConfig struct {
Attempts int
RenotifyInterval time.Duration
Params map[string]interface{} `yaml:",inline"`
Attempts int
RenotifyInterval time.Duration
Params map[string]interface{} `yaml:",inline"`
}

// APIConfig is the configuration for the API service.
type APIConfig struct {
Port int
HealthPort int
Timeout time.Duration
PaginationKey string
CertFile, KeyFile, CAFile string
}

Expand All @@ -71,8 +73,8 @@ var DefaultConfig = Config{
Timeout: 900 * time.Second,
},
Notifier: &NotifierConfig{
Attempts: 5,
RenotifyInterval: 2 * time.Hour,
Attempts: 5,
RenotifyInterval: 2 * time.Hour,
},
}

Expand All @@ -96,5 +98,22 @@ func Load(path string) (config *Config, err error) {
}

err = yaml.Unmarshal(d, config)
if err != nil {
return
}

if config.API.PaginationKey == "" {
var key fernet.Key
if err = key.Generate(); err != nil {
return
}
config.API.PaginationKey = key.Encode()
} else {
_, err = fernet.DecodeKey(config.API.PaginationKey)
if err != nil {
return
}
}

return
}
20 changes: 20 additions & 0 deletions vendor/github.com/fernet/fernet-go/License

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 22 additions & 0 deletions vendor/github.com/fernet/fernet-go/Readme

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions vendor/github.com/fernet/fernet-go/cmd/fernet-keygen/main.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

45 changes: 45 additions & 0 deletions vendor/github.com/fernet/fernet-go/cmd/fernet-sign/main.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit d19a434

Please sign in to comment.