mirror of https://github.com/docker/docs.git
Merge pull request #339 from cyli/server-handler
Get Snapshot Key Handler
This commit is contained in:
commit
d9419287ea
|
@ -208,46 +208,70 @@ func getSnapshot(ctx context.Context, w http.ResponseWriter, logger ctxu.Logger,
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTimestampKeyHandler returns a timestamp public key, creating a new key-pair
|
// GetKeyHandler returns a public key for the specified role, creating a new key-pair
|
||||||
// it if it doesn't yet exist
|
// it if it doesn't yet exist
|
||||||
func GetTimestampKeyHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
|
func GetKeyHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
|
||||||
|
defer r.Body.Close()
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
gun := vars["imageName"]
|
return getKeyHandler(ctx, w, r, vars)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getKeyHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
gun, ok := vars["imageName"]
|
||||||
|
if !ok || gun == "" {
|
||||||
|
return errors.ErrUnknown.WithDetail("no gun")
|
||||||
|
}
|
||||||
|
role, ok := vars["tufRole"]
|
||||||
|
if !ok || role == "" {
|
||||||
|
return errors.ErrUnknown.WithDetail("no role")
|
||||||
|
}
|
||||||
|
|
||||||
logger := ctxu.GetLoggerWithField(ctx, gun, "gun")
|
logger := ctxu.GetLoggerWithField(ctx, gun, "gun")
|
||||||
|
|
||||||
s := ctx.Value("metaStore")
|
s := ctx.Value("metaStore")
|
||||||
store, ok := s.(storage.MetaStore)
|
store, ok := s.(storage.MetaStore)
|
||||||
if !ok {
|
if !ok || store == nil {
|
||||||
logger.Error("500 GET storage not configured")
|
logger.Error("500 GET storage not configured")
|
||||||
return errors.ErrNoStorage.WithDetail(nil)
|
return errors.ErrNoStorage.WithDetail(nil)
|
||||||
}
|
}
|
||||||
c := ctx.Value("cryptoService")
|
c := ctx.Value("cryptoService")
|
||||||
crypto, ok := c.(signed.CryptoService)
|
crypto, ok := c.(signed.CryptoService)
|
||||||
if !ok {
|
if !ok || crypto == nil {
|
||||||
logger.Error("500 GET crypto service not configured")
|
logger.Error("500 GET crypto service not configured")
|
||||||
return errors.ErrNoCryptoService.WithDetail(nil)
|
return errors.ErrNoCryptoService.WithDetail(nil)
|
||||||
}
|
}
|
||||||
algo := ctx.Value("keyAlgorithm")
|
algo := ctx.Value("keyAlgorithm")
|
||||||
keyAlgo, ok := algo.(string)
|
keyAlgo, ok := algo.(string)
|
||||||
if !ok {
|
if !ok || keyAlgo == "" {
|
||||||
logger.Error("500 GET key algorithm not configured")
|
logger.Error("500 GET key algorithm not configured")
|
||||||
return errors.ErrNoKeyAlgorithm.WithDetail(nil)
|
return errors.ErrNoKeyAlgorithm.WithDetail(nil)
|
||||||
}
|
}
|
||||||
keyAlgorithm := keyAlgo
|
keyAlgorithm := keyAlgo
|
||||||
|
|
||||||
key, err := timestamp.GetOrCreateTimestampKey(gun, store, crypto, keyAlgorithm)
|
var (
|
||||||
|
key data.PublicKey
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
switch role {
|
||||||
|
case data.CanonicalTimestampRole:
|
||||||
|
key, err = timestamp.GetOrCreateTimestampKey(gun, store, crypto, keyAlgorithm)
|
||||||
|
case data.CanonicalSnapshotRole:
|
||||||
|
key, err = snapshot.GetOrCreateSnapshotKey(gun, store, crypto, keyAlgorithm)
|
||||||
|
default:
|
||||||
|
logger.Errorf("400 GET %s key: %v", role, err)
|
||||||
|
return errors.ErrInvalidRole.WithDetail(role)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("500 GET timestamp key: %v", err)
|
logger.Errorf("500 GET %s key: %v", role, err)
|
||||||
return errors.ErrUnknown.WithDetail(err)
|
return errors.ErrUnknown.WithDetail(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
out, err := json.Marshal(key)
|
out, err := json.Marshal(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("500 GET timestamp key")
|
logger.Errorf("500 GET %s key", role)
|
||||||
return errors.ErrUnknown.WithDetail(err)
|
return errors.ErrUnknown.WithDetail(err)
|
||||||
}
|
}
|
||||||
logger.Debug("200 GET timestamp key")
|
logger.Debugf("200 GET %s key", role)
|
||||||
w.Write(out)
|
w.Write(out)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
|
||||||
|
ctxu "github.com/docker/distribution/context"
|
||||||
"github.com/docker/notary/server/storage"
|
"github.com/docker/notary/server/storage"
|
||||||
"github.com/docker/notary/tuf/data"
|
"github.com/docker/notary/tuf/data"
|
||||||
"github.com/docker/notary/tuf/signed"
|
"github.com/docker/notary/tuf/signed"
|
||||||
|
@ -19,6 +20,29 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type handlerState struct {
|
||||||
|
// interface{} so we can test invalid values
|
||||||
|
store interface{}
|
||||||
|
crypto interface{}
|
||||||
|
keyAlgo interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultState() handlerState {
|
||||||
|
return handlerState{
|
||||||
|
store: storage.NewMemStorage(),
|
||||||
|
crypto: signed.NewEd25519(),
|
||||||
|
keyAlgo: data.ED25519Key,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getContext(h handlerState) context.Context {
|
||||||
|
ctx := context.Background()
|
||||||
|
ctx = context.WithValue(ctx, "metaStore", h.store)
|
||||||
|
ctx = context.WithValue(ctx, "keyAlgorithm", h.keyAlgo)
|
||||||
|
ctx = context.WithValue(ctx, "cryptoService", h.crypto)
|
||||||
|
return ctxu.WithLogger(ctx, ctxu.GetRequestLogger(ctx))
|
||||||
|
}
|
||||||
|
|
||||||
func TestMainHandlerGet(t *testing.T) {
|
func TestMainHandlerGet(t *testing.T) {
|
||||||
hand := utils.RootHandlerFactory(nil, context.Background(), &signed.Ed25519{})
|
hand := utils.RootHandlerFactory(nil, context.Background(), &signed.Ed25519{})
|
||||||
handler := hand(MainHandler)
|
handler := hand(MainHandler)
|
||||||
|
@ -46,6 +70,102 @@ func TestMainHandlerNotGet(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetKeyHandler needs to have access to a metadata store and cryptoservice,
|
||||||
|
// a key algorithm
|
||||||
|
func TestGetKeyHandlerInvalidConfiguration(t *testing.T) {
|
||||||
|
noStore := defaultState()
|
||||||
|
noStore.store = nil
|
||||||
|
|
||||||
|
invalidStore := defaultState()
|
||||||
|
invalidStore.store = "not a store"
|
||||||
|
|
||||||
|
noCrypto := defaultState()
|
||||||
|
noCrypto.crypto = nil
|
||||||
|
|
||||||
|
invalidCrypto := defaultState()
|
||||||
|
invalidCrypto.crypto = "not a cryptoservice"
|
||||||
|
|
||||||
|
noKeyAlgo := defaultState()
|
||||||
|
noKeyAlgo.keyAlgo = ""
|
||||||
|
|
||||||
|
invalidKeyAlgo := defaultState()
|
||||||
|
invalidKeyAlgo.keyAlgo = 1
|
||||||
|
|
||||||
|
invalidStates := map[string][]handlerState{
|
||||||
|
"no storage": {noStore, invalidStore},
|
||||||
|
"no cryptoservice": {noCrypto, invalidCrypto},
|
||||||
|
"no keyalgorithm": {noKeyAlgo, invalidKeyAlgo},
|
||||||
|
}
|
||||||
|
|
||||||
|
vars := map[string]string{
|
||||||
|
"imageName": "gun",
|
||||||
|
"tufRole": data.CanonicalTimestampRole,
|
||||||
|
}
|
||||||
|
req := &http.Request{Body: ioutil.NopCloser(bytes.NewBuffer(nil))}
|
||||||
|
for errString, states := range invalidStates {
|
||||||
|
for _, s := range states {
|
||||||
|
err := getKeyHandler(getContext(s), httptest.NewRecorder(), req, vars)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), errString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetKeyHandler needs to be set up such that an imageName and tufRole are both
|
||||||
|
// provided and non-empty.
|
||||||
|
func TestGetKeyHandlerNoRoleOrRepo(t *testing.T) {
|
||||||
|
state := defaultState()
|
||||||
|
req := &http.Request{Body: ioutil.NopCloser(bytes.NewBuffer(nil))}
|
||||||
|
|
||||||
|
for _, key := range []string{"imageName", "tufRole"} {
|
||||||
|
vars := map[string]string{
|
||||||
|
"imageName": "gun",
|
||||||
|
"tufRole": data.CanonicalTimestampRole,
|
||||||
|
}
|
||||||
|
|
||||||
|
// not provided
|
||||||
|
delete(vars, key)
|
||||||
|
err := getKeyHandler(getContext(state), httptest.NewRecorder(), req, vars)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "unknown")
|
||||||
|
|
||||||
|
// empty
|
||||||
|
vars[key] = ""
|
||||||
|
err = getKeyHandler(getContext(state), httptest.NewRecorder(), req, vars)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "unknown")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getting a key for a non-supported role results in a 400.
|
||||||
|
func TestGetKeyHandlerInvalidRole(t *testing.T) {
|
||||||
|
state := defaultState()
|
||||||
|
vars := map[string]string{
|
||||||
|
"imageName": "gun",
|
||||||
|
"tufRole": data.CanonicalRootRole,
|
||||||
|
}
|
||||||
|
req := &http.Request{Body: ioutil.NopCloser(bytes.NewBuffer(nil))}
|
||||||
|
|
||||||
|
err := getKeyHandler(getContext(state), httptest.NewRecorder(), req, vars)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "invalid role")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getting the key for a valid role and gun succeeds
|
||||||
|
func TestGetKeyHandlerCreatesOnce(t *testing.T) {
|
||||||
|
state := defaultState()
|
||||||
|
roles := []string{data.CanonicalTimestampRole, data.CanonicalSnapshotRole}
|
||||||
|
req := &http.Request{Body: ioutil.NopCloser(bytes.NewBuffer(nil))}
|
||||||
|
|
||||||
|
for _, role := range roles {
|
||||||
|
vars := map[string]string{"imageName": "gun", "tufRole": role}
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
err := getKeyHandler(getContext(state), recorder, req, vars)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.True(t, len(recorder.Body.String()) > 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetHandlerRoot(t *testing.T) {
|
func TestGetHandlerRoot(t *testing.T) {
|
||||||
store := storage.NewMemStorage()
|
store := storage.NewMemStorage()
|
||||||
_, repo, _ := testutils.EmptyRepo()
|
_, repo, _ := testutils.EmptyRepo()
|
||||||
|
@ -77,9 +197,7 @@ func TestGetHandlerTimestamp(t *testing.T) {
|
||||||
store := storage.NewMemStorage()
|
store := storage.NewMemStorage()
|
||||||
_, repo, crypto := testutils.EmptyRepo()
|
_, repo, crypto := testutils.EmptyRepo()
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := getContext(handlerState{store: store, crypto: crypto})
|
||||||
ctx = context.WithValue(ctx, "metaStore", store)
|
|
||||||
ctx = context.WithValue(ctx, "cryptoService", crypto)
|
|
||||||
|
|
||||||
sn, err := repo.SignSnapshot(data.DefaultExpires("snapshot"))
|
sn, err := repo.SignSnapshot(data.DefaultExpires("snapshot"))
|
||||||
snJSON, err := json.Marshal(sn)
|
snJSON, err := json.Marshal(sn)
|
||||||
|
@ -110,9 +228,7 @@ func TestGetHandlerSnapshot(t *testing.T) {
|
||||||
store := storage.NewMemStorage()
|
store := storage.NewMemStorage()
|
||||||
_, repo, crypto := testutils.EmptyRepo()
|
_, repo, crypto := testutils.EmptyRepo()
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := getContext(handlerState{store: store, crypto: crypto})
|
||||||
ctx = context.WithValue(ctx, "metaStore", store)
|
|
||||||
ctx = context.WithValue(ctx, "cryptoService", crypto)
|
|
||||||
|
|
||||||
sn, err := repo.SignSnapshot(data.DefaultExpires("snapshot"))
|
sn, err := repo.SignSnapshot(data.DefaultExpires("snapshot"))
|
||||||
snJSON, err := json.Marshal(sn)
|
snJSON, err := json.Marshal(sn)
|
||||||
|
|
|
@ -93,10 +93,11 @@ func RootHandler(ac auth.AccessController, ctx context.Context, trust signed.Cry
|
||||||
prometheus.InstrumentHandlerWithOpts(
|
prometheus.InstrumentHandlerWithOpts(
|
||||||
prometheusOpts("GetRole"),
|
prometheusOpts("GetRole"),
|
||||||
hand(handlers.GetHandler, "pull")))
|
hand(handlers.GetHandler, "pull")))
|
||||||
r.Methods("GET").Path("/v2/{imageName:.*}/_trust/tuf/timestamp.key").Handler(
|
r.Methods("GET").Path(
|
||||||
|
"/v2/{imageName:.*}/_trust/tuf/{tufRole:(snapshot|timestamp)}.key").Handler(
|
||||||
prometheus.InstrumentHandlerWithOpts(
|
prometheus.InstrumentHandlerWithOpts(
|
||||||
prometheusOpts("GetTimestampKey"),
|
prometheusOpts("GetKey"),
|
||||||
hand(handlers.GetTimestampKeyHandler, "push", "pull")))
|
hand(handlers.GetKeyHandler, "push", "pull")))
|
||||||
r.Methods("DELETE").Path("/v2/{imageName:.*}/_trust/tuf/").Handler(
|
r.Methods("DELETE").Path("/v2/{imageName:.*}/_trust/tuf/").Handler(
|
||||||
prometheus.InstrumentHandlerWithOpts(
|
prometheus.InstrumentHandlerWithOpts(
|
||||||
prometheusOpts("DeleteTuf"),
|
prometheusOpts("DeleteTuf"),
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
@ -8,6 +9,8 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
_ "github.com/docker/distribution/registry/auth/silly"
|
_ "github.com/docker/distribution/registry/auth/silly"
|
||||||
|
"github.com/docker/notary/server/storage"
|
||||||
|
"github.com/docker/notary/tuf/data"
|
||||||
"github.com/docker/notary/tuf/signed"
|
"github.com/docker/notary/tuf/signed"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
@ -56,3 +59,29 @@ func TestMetricsEndpoint(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, http.StatusOK, res.StatusCode)
|
assert.Equal(t, http.StatusOK, res.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetKeys supports only the timestamp and snapshot key endpoints
|
||||||
|
func TestGetKeysEndpoint(t *testing.T) {
|
||||||
|
ctx := context.WithValue(
|
||||||
|
context.Background(), "metaStore", storage.NewMemStorage())
|
||||||
|
ctx = context.WithValue(ctx, "keyAlgorithm", data.ED25519Key)
|
||||||
|
|
||||||
|
handler := RootHandler(nil, ctx, signed.NewEd25519())
|
||||||
|
ts := httptest.NewServer(handler)
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
rolesToStatus := map[string]int{
|
||||||
|
data.CanonicalTimestampRole: http.StatusOK,
|
||||||
|
data.CanonicalSnapshotRole: http.StatusOK,
|
||||||
|
data.CanonicalTargetsRole: http.StatusNotFound,
|
||||||
|
data.CanonicalRootRole: http.StatusNotFound,
|
||||||
|
"somerandomrole": http.StatusNotFound,
|
||||||
|
}
|
||||||
|
|
||||||
|
for role, expectedStatus := range rolesToStatus {
|
||||||
|
res, err := http.Get(
|
||||||
|
fmt.Sprintf("%s/v2/gun/_trust/tuf/%s.key", ts.URL, role))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, expectedStatus, res.StatusCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -46,9 +46,11 @@ func (e *Ed25519) RemoveKey(keyID string) error {
|
||||||
// ListKeys returns the list of keys IDs for the role
|
// ListKeys returns the list of keys IDs for the role
|
||||||
func (e *Ed25519) ListKeys(role string) []string {
|
func (e *Ed25519) ListKeys(role string) []string {
|
||||||
keyIDs := make([]string, 0, len(e.keys))
|
keyIDs := make([]string, 0, len(e.keys))
|
||||||
for id := range e.keys {
|
for id, edCryptoKey := range e.keys {
|
||||||
|
if edCryptoKey.role == role {
|
||||||
keyIDs = append(keyIDs, id)
|
keyIDs = append(keyIDs, id)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return keyIDs
|
return keyIDs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
package signed
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/notary/tuf/data"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ListKeys only returns the keys for that role
|
||||||
|
func TestListKeys(t *testing.T) {
|
||||||
|
c := NewEd25519()
|
||||||
|
tskey, err := c.Create(data.CanonicalTimestampRole, data.ED25519Key)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = c.Create(data.CanonicalRootRole, data.ED25519Key)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
tsKeys := c.ListKeys(data.CanonicalTimestampRole)
|
||||||
|
assert.Len(t, tsKeys, 1)
|
||||||
|
assert.Equal(t, tskey.ID(), tsKeys[0])
|
||||||
|
|
||||||
|
assert.Len(t, c.ListKeys(data.CanonicalTargetsRole), 0)
|
||||||
|
}
|
Loading…
Reference in New Issue