Skip to content

Commit

Permalink
api: v2 api with gRPC and gRPC-gateway
Browse files Browse the repository at this point in the history
Newly designed API defines Ancestry as a set of layers
and shrinked the api to only the most used apis:
post ancestry, get layer, get notification, delete notification

Fixes #98
  • Loading branch information
KeyboardNerd committed Jun 13, 2017
1 parent abd7d2e commit a4edf38
Show file tree
Hide file tree
Showing 18 changed files with 2,535 additions and 39 deletions.
4 changes: 4 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
language: go

go:
- 1.8

sudo: required

install:
Expand Down
13 changes: 13 additions & 0 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
log "github.com/sirupsen/logrus"
"github.com/tylerb/graceful"

"github.com/coreos/clair/api/v2"
"github.com/coreos/clair/database"
"github.com/coreos/clair/pkg/stopper"
)
Expand All @@ -35,12 +36,24 @@ const timeoutResponse = `{"Error":{"Message":"Clair failed to respond within the
// Config is the configuration for the API service.
type Config struct {
Port int
GrpcPort int
HealthPort int
Timeout time.Duration
PaginationKey string
CertFile, KeyFile, CAFile string
}

func RunV2(cfg *Config, store database.Datastore) {
tlsConfig, err := tlsClientConfig(cfg.CAFile)
if err != nil {
log.WithError(err).Fatal("could not initialize client cert authentication")
}
if tlsConfig != nil {
log.Info("main API configured with client certificate authentication")
}
v2.Run(cfg.GrpcPort, tlsConfig, cfg.PaginationKey, cfg.CertFile, cfg.KeyFile, store)
}

func Run(cfg *Config, store database.Datastore, st *stopper.Stopper) {
defer st.End()

Expand Down
49 changes: 49 additions & 0 deletions api/token/token.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright 2017 clair authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package token implements encryption/decryption for json encoded interfaces
package token

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

"github.com/fernet/fernet-go"
)

// Unmarshal decrypts a token using provided key
// and decode the result into interface.
func Unmarshal(token string, key string, v interface{}) error {
k, _ := fernet.DecodeKey(key)
msg := fernet.VerifyAndDecrypt([]byte(token), time.Hour, []*fernet.Key{k})
if msg == nil {
return errors.New("invalid or expired pagination token")
}

return json.NewDecoder(bytes.NewBuffer(msg)).Decode(&v)
}

// Marshal encodes an interface into json bytes and encrypts it.
func Marshal(v interface{}, key string) ([]byte, error) {
var buf bytes.Buffer
err := json.NewEncoder(&buf).Encode(v)
if err != nil {
return nil, err
}

k, _ := fernet.DecodeKey(key)
return fernet.EncryptAndSign(buf.Bytes(), k)
}
30 changes: 2 additions & 28 deletions api/v1/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,9 @@
package v1

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

"github.com/fernet/fernet-go"

"github.com/coreos/clair/api/token"
"github.com/coreos/clair/database"
"github.com/coreos/clair/ext/versionfmt"
)
Expand Down Expand Up @@ -227,7 +222,7 @@ func NotificationFromDatabaseModel(dbNotification database.VulnerabilityNotifica

var nextPageStr string
if nextPage != database.NoVulnerabilityNotificationPage {
nextPageBytes, _ := tokenMarshal(nextPage, key)
nextPageBytes, _ := token.Marshal(nextPage, key)
nextPageStr = string(nextPageBytes)
}

Expand Down Expand Up @@ -320,24 +315,3 @@ type FeatureEnvelope struct {
Features *[]Feature `json:"Features,omitempty"`
Error *Error `json:"Error,omitempty"`
}

func tokenUnmarshal(token string, key string, v interface{}) error {
k, _ := fernet.DecodeKey(key)
msg := fernet.VerifyAndDecrypt([]byte(token), time.Hour, []*fernet.Key{k})
if msg == nil {
return errors.New("invalid or expired pagination token")
}

return json.NewDecoder(bytes.NewBuffer(msg)).Decode(&v)
}

func tokenMarshal(v interface{}, key string) ([]byte, error) {
var buf bytes.Buffer
err := json.NewEncoder(&buf).Encode(v)
if err != nil {
return nil, err
}

k, _ := fernet.DecodeKey(key)
return fernet.EncryptAndSign(buf.Bytes(), k)
}
9 changes: 5 additions & 4 deletions api/v1/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
log "github.com/sirupsen/logrus"

"github.com/coreos/clair"
"github.com/coreos/clair/api/token"
"github.com/coreos/clair/database"
"github.com/coreos/clair/pkg/commonerr"
"github.com/coreos/clair/pkg/tarutil"
Expand Down Expand Up @@ -209,7 +210,7 @@ func getVulnerabilities(w http.ResponseWriter, r *http.Request, p httprouter.Par
page := 0
pageStrs, pageExists := query["page"]
if pageExists {
err = tokenUnmarshal(pageStrs[0], ctx.PaginationKey, &page)
err = token.Unmarshal(pageStrs[0], ctx.PaginationKey, &page)
if err != nil {
writeResponse(w, r, http.StatusBadRequest, VulnerabilityEnvelope{Error: &Error{"invalid page format: " + err.Error()}})
return getNotificationRoute, http.StatusBadRequest
Expand Down Expand Up @@ -239,7 +240,7 @@ func getVulnerabilities(w http.ResponseWriter, r *http.Request, p httprouter.Par

var nextPageStr string
if nextPage != -1 {
nextPageBytes, err := tokenMarshal(nextPage, ctx.PaginationKey)
nextPageBytes, err := token.Marshal(nextPage, ctx.PaginationKey)
if err != nil {
writeResponse(w, r, http.StatusBadRequest, VulnerabilityEnvelope{Error: &Error{"failed to marshal token: " + err.Error()}})
return getNotificationRoute, http.StatusBadRequest
Expand Down Expand Up @@ -452,14 +453,14 @@ func getNotification(w http.ResponseWriter, r *http.Request, p httprouter.Params
page := database.VulnerabilityNotificationFirstPage
pageStrs, pageExists := query["page"]
if pageExists {
err := tokenUnmarshal(pageStrs[0], ctx.PaginationKey, &page)
err := token.Unmarshal(pageStrs[0], ctx.PaginationKey, &page)
if err != nil {
writeResponse(w, r, http.StatusBadRequest, NotificationEnvelope{Error: &Error{"invalid page format: " + err.Error()}})
return getNotificationRoute, http.StatusBadRequest
}
pageToken = pageStrs[0]
} else {
pageTokenBytes, err := tokenMarshal(page, ctx.PaginationKey)
pageTokenBytes, err := token.Marshal(page, ctx.PaginationKey)
if err != nil {
writeResponse(w, r, http.StatusBadRequest, NotificationEnvelope{Error: &Error{"failed to marshal token: " + err.Error()}})
return getNotificationRoute, http.StatusBadRequest
Expand Down
17 changes: 17 additions & 0 deletions api/v2/clairpb/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
all:
protoc -I/usr/local/include -I. \
-I${GOPATH}/src \
-I${GOPATH}/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
--go_out=plugins=grpc:. \
clair.proto
protoc -I/usr/local/include -I. \
-I${GOPATH}/src \
-I${GOPATH}/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
--grpc-gateway_out=logtostderr=true:. \
clair.proto
protoc -I/usr/local/include -I. \
-I${GOPATH}/src \
-I${GOPATH}/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
--swagger_out=logtostderr=true:. \
clair.proto
go generate .

0 comments on commit a4edf38

Please sign in to comment.