Merge pull request #472 from riyazdf/docker-trust

docker trust: view, revoke, sign subcommands (experimental)
This commit is contained in:
Victor Vieux 2017-09-25 17:26:52 -07:00 committed by GitHub
commit af3cdccf52
111 changed files with 6990 additions and 3707 deletions

View File

@ -12,12 +12,14 @@ import (
cliconfig "github.com/docker/cli/cli/config" cliconfig "github.com/docker/cli/cli/config"
"github.com/docker/cli/cli/config/configfile" "github.com/docker/cli/cli/config/configfile"
cliflags "github.com/docker/cli/cli/flags" cliflags "github.com/docker/cli/cli/flags"
"github.com/docker/cli/cli/trust"
dopts "github.com/docker/cli/opts" dopts "github.com/docker/cli/opts"
"github.com/docker/docker/api" "github.com/docker/docker/api"
"github.com/docker/docker/client" "github.com/docker/docker/client"
"github.com/docker/go-connections/sockets" "github.com/docker/go-connections/sockets"
"github.com/docker/go-connections/tlsconfig" "github.com/docker/go-connections/tlsconfig"
"github.com/docker/notary" "github.com/docker/notary"
notaryclient "github.com/docker/notary/client"
"github.com/docker/notary/passphrase" "github.com/docker/notary/passphrase"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -40,6 +42,7 @@ type Cli interface {
SetIn(in *InStream) SetIn(in *InStream)
ConfigFile() *configfile.ConfigFile ConfigFile() *configfile.ConfigFile
ServerInfo() ServerInfo ServerInfo() ServerInfo
NotaryClient(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (notaryclient.Repository, error)
} }
// DockerCli is an instance the docker command line client. // DockerCli is an instance the docker command line client.
@ -161,6 +164,11 @@ func getClientWithPassword(passRetriever notary.PassRetriever, newClient func(pa
} }
} }
// NotaryClient provides a Notary Repository to interact with signed metadata for an image
func (cli *DockerCli) NotaryClient(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (notaryclient.Repository, error) {
return trust.GetNotaryRepository(cli.In(), cli.Out(), UserAgent(), imgRefAndAuth.RepoInfo(), imgRefAndAuth.AuthConfig(), actions...)
}
// ServerInfo stores details about the supported features and platform of the // ServerInfo stores details about the supported features and platform of the
// server // server
type ServerInfo struct { type ServerInfo struct {

View File

@ -17,6 +17,7 @@ import (
"github.com/docker/cli/cli/command/stack" "github.com/docker/cli/cli/command/stack"
"github.com/docker/cli/cli/command/swarm" "github.com/docker/cli/cli/command/swarm"
"github.com/docker/cli/cli/command/system" "github.com/docker/cli/cli/command/system"
"github.com/docker/cli/cli/command/trust"
"github.com/docker/cli/cli/command/volume" "github.com/docker/cli/cli/command/volume"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -69,6 +70,9 @@ func AddCommands(cmd *cobra.Command, dockerCli *command.DockerCli) {
// swarm // swarm
swarm.NewSwarmCommand(dockerCli), swarm.NewSwarmCommand(dockerCli),
// trust
trust.NewTrustCommand(dockerCli),
// volume // volume
volume.NewVolumeCommand(dockerCli), volume.NewVolumeCommand(dockerCli),

View File

@ -0,0 +1,150 @@
package formatter
import (
"sort"
"strings"
"github.com/docker/docker/pkg/stringid"
)
const (
defaultTrustTagTableFormat = "table {{.SignedTag}}\t{{.Digest}}\t{{.Signers}}"
signedTagNameHeader = "SIGNED TAG"
trustedDigestHeader = "DIGEST"
signersHeader = "SIGNERS"
defaultSignerInfoTableFormat = "table {{.Signer}}\t{{.Keys}}"
signerNameHeader = "SIGNER"
keysHeader = "KEYS"
)
// SignedTagInfo represents all formatted information needed to describe a signed tag:
// Name: name of the signed tag
// Digest: hex encoded digest of the contents
// Signers: list of entities who signed the tag
type SignedTagInfo struct {
Name string
Digest string
Signers []string
}
// SignerInfo represents all formatted information needed to describe a signer:
// Name: name of the signer role
// Keys: the keys associated with the signer
type SignerInfo struct {
Name string
Keys []string
}
// NewTrustTagFormat returns a Format for rendering using a trusted tag Context
func NewTrustTagFormat() Format {
return defaultTrustTagTableFormat
}
// NewSignerInfoFormat returns a Format for rendering a signer role info Context
func NewSignerInfoFormat() Format {
return defaultSignerInfoTableFormat
}
// TrustTagWrite writes the context
func TrustTagWrite(ctx Context, signedTagInfoList []SignedTagInfo) error {
render := func(format func(subContext subContext) error) error {
for _, signedTag := range signedTagInfoList {
if err := format(&trustTagContext{s: signedTag}); err != nil {
return err
}
}
return nil
}
trustTagCtx := trustTagContext{}
trustTagCtx.header = trustTagHeaderContext{
"SignedTag": signedTagNameHeader,
"Digest": trustedDigestHeader,
"Signers": signersHeader,
}
return ctx.Write(&trustTagCtx, render)
}
type trustTagHeaderContext map[string]string
type trustTagContext struct {
HeaderContext
s SignedTagInfo
}
// SignedTag returns the name of the signed tag
func (c *trustTagContext) SignedTag() string {
return c.s.Name
}
// Digest returns the hex encoded digest associated with this signed tag
func (c *trustTagContext) Digest() string {
return c.s.Digest
}
// Signers returns the sorted list of entities who signed this tag
func (c *trustTagContext) Signers() string {
sort.Strings(c.s.Signers)
return strings.Join(c.s.Signers, ", ")
}
// SignerInfoWrite writes the context
func SignerInfoWrite(ctx Context, signerInfoList []SignerInfo) error {
render := func(format func(subContext subContext) error) error {
for _, signerInfo := range signerInfoList {
if err := format(&signerInfoContext{
trunc: ctx.Trunc,
s: signerInfo,
}); err != nil {
return err
}
}
return nil
}
signerInfoCtx := signerInfoContext{}
signerInfoCtx.header = signerInfoHeaderContext{
"Signer": signerNameHeader,
"Keys": keysHeader,
}
return ctx.Write(&signerInfoCtx, render)
}
type signerInfoHeaderContext map[string]string
type signerInfoContext struct {
HeaderContext
trunc bool
s SignerInfo
}
// Keys returns the sorted list of keys associated with the signer
func (c *signerInfoContext) Keys() string {
sort.Strings(c.s.Keys)
truncatedKeys := []string{}
if c.trunc {
for _, keyID := range c.s.Keys {
truncatedKeys = append(truncatedKeys, stringid.TruncateID(keyID))
}
return strings.Join(truncatedKeys, ", ")
}
return strings.Join(c.s.Keys, ", ")
}
// Signer returns the name of the signer
func (c *signerInfoContext) Signer() string {
return c.s.Name
}
// SignerInfoList helps sort []SignerInfo by signer names
type SignerInfoList []SignerInfo
func (signerInfoComp SignerInfoList) Len() int {
return len(signerInfoComp)
}
func (signerInfoComp SignerInfoList) Less(i, j int) bool {
return signerInfoComp[i].Name < signerInfoComp[j].Name
}
func (signerInfoComp SignerInfoList) Swap(i, j int) {
signerInfoComp[i], signerInfoComp[j] = signerInfoComp[j], signerInfoComp[i]
}

View File

@ -0,0 +1,238 @@
package formatter
import (
"bytes"
"testing"
"github.com/docker/docker/pkg/stringid"
"github.com/stretchr/testify/assert"
)
func TestTrustTag(t *testing.T) {
digest := stringid.GenerateRandomID()
trustedTag := "tag"
var ctx trustTagContext
cases := []struct {
trustTagCtx trustTagContext
expValue string
call func() string
}{
{
trustTagContext{
s: SignedTagInfo{Name: trustedTag,
Digest: digest,
Signers: nil,
},
},
digest,
ctx.Digest,
},
{
trustTagContext{
s: SignedTagInfo{Name: trustedTag,
Digest: digest,
Signers: nil,
},
},
trustedTag,
ctx.SignedTag,
},
// Empty signers makes a row with empty string
{
trustTagContext{
s: SignedTagInfo{Name: trustedTag,
Digest: digest,
Signers: nil,
},
},
"",
ctx.Signers,
},
{
trustTagContext{
s: SignedTagInfo{Name: trustedTag,
Digest: digest,
Signers: []string{"alice", "bob", "claire"},
},
},
"alice, bob, claire",
ctx.Signers,
},
// alphabetic signing on Signers
{
trustTagContext{
s: SignedTagInfo{Name: trustedTag,
Digest: digest,
Signers: []string{"claire", "bob", "alice"},
},
},
"alice, bob, claire",
ctx.Signers,
},
}
for _, c := range cases {
ctx = c.trustTagCtx
v := c.call()
if v != c.expValue {
t.Fatalf("Expected %s, was %s\n", c.expValue, v)
}
}
}
func TestTrustTagContextWrite(t *testing.T) {
cases := []struct {
context Context
expected string
}{
// Errors
{
Context{
Format: "{{InvalidFunction}}",
},
`Template parsing error: template: :1: function "InvalidFunction" not defined
`,
},
{
Context{
Format: "{{nil}}",
},
`Template parsing error: template: :1:2: executing "" at <nil>: nil is not a command
`,
},
// Table Format
{
Context{
Format: NewTrustTagFormat(),
},
`SIGNED TAG DIGEST SIGNERS
tag1 deadbeef alice
tag2 aaaaaaaa alice, bob
tag3 bbbbbbbb
`,
},
}
for _, testcase := range cases {
signedTags := []SignedTagInfo{
{Name: "tag1", Digest: "deadbeef", Signers: []string{"alice"}},
{Name: "tag2", Digest: "aaaaaaaa", Signers: []string{"alice", "bob"}},
{Name: "tag3", Digest: "bbbbbbbb", Signers: []string{}},
}
out := bytes.NewBufferString("")
testcase.context.Output = out
err := TrustTagWrite(testcase.context, signedTags)
if err != nil {
assert.EqualError(t, err, testcase.expected)
} else {
assert.Equal(t, testcase.expected, out.String())
}
}
}
// With no trust data, the TrustTagWrite will print an empty table:
// it's up to the caller to decide whether or not to print this versus an error
func TestTrustTagContextEmptyWrite(t *testing.T) {
emptyCase := struct {
context Context
expected string
}{
Context{
Format: NewTrustTagFormat(),
},
`SIGNED TAG DIGEST SIGNERS
`,
}
emptySignedTags := []SignedTagInfo{}
out := bytes.NewBufferString("")
emptyCase.context.Output = out
err := TrustTagWrite(emptyCase.context, emptySignedTags)
assert.NoError(t, err)
assert.Equal(t, emptyCase.expected, out.String())
}
func TestSignerInfoContextEmptyWrite(t *testing.T) {
emptyCase := struct {
context Context
expected string
}{
Context{
Format: NewSignerInfoFormat(),
},
`SIGNER KEYS
`,
}
emptySignerInfo := []SignerInfo{}
out := bytes.NewBufferString("")
emptyCase.context.Output = out
err := SignerInfoWrite(emptyCase.context, emptySignerInfo)
assert.NoError(t, err)
assert.Equal(t, emptyCase.expected, out.String())
}
func TestSignerInfoContextWrite(t *testing.T) {
cases := []struct {
context Context
expected string
}{
// Errors
{
Context{
Format: "{{InvalidFunction}}",
},
`Template parsing error: template: :1: function "InvalidFunction" not defined
`,
},
{
Context{
Format: "{{nil}}",
},
`Template parsing error: template: :1:2: executing "" at <nil>: nil is not a command
`,
},
// Table Format
{
Context{
Format: NewSignerInfoFormat(),
Trunc: true,
},
`SIGNER KEYS
alice key11, key12
bob key21
eve foobarbazqux, key31, key32
`,
},
// No truncation
{
Context{
Format: NewSignerInfoFormat(),
},
`SIGNER KEYS
alice key11, key12
bob key21
eve foobarbazquxquux, key31, key32
`,
},
}
for _, testcase := range cases {
signerInfo := SignerInfoList{
{Name: "alice", Keys: []string{"key11", "key12"}},
{Name: "bob", Keys: []string{"key21"}},
{Name: "eve", Keys: []string{"key31", "key32", "foobarbazquxquux"}},
}
out := bytes.NewBufferString("")
testcase.context.Output = out
err := SignerInfoWrite(testcase.context, signerInfo)
if err != nil {
assert.EqualError(t, err, testcase.expected)
} else {
assert.Equal(t, testcase.expected, out.String())
}
}
}

View File

@ -48,7 +48,7 @@ func runPush(dockerCli command.Cli, remote string) error {
requestPrivilege := command.RegistryAuthenticationPrivilegedFunc(dockerCli, repoInfo.Index, "push") requestPrivilege := command.RegistryAuthenticationPrivilegedFunc(dockerCli, repoInfo.Index, "push")
if command.IsTrusted() { if command.IsTrusted() {
return trustedPush(ctx, dockerCli, repoInfo, ref, authConfig, requestPrivilege) return TrustedPush(ctx, dockerCli, repoInfo, ref, authConfig, requestPrivilege)
} }
responseBody, err := imagePushPrivileged(ctx, dockerCli, authConfig, ref, requestPrivilege) responseBody, err := imagePushPrivileged(ctx, dockerCli, authConfig, ref, requestPrivilege)

View File

@ -5,7 +5,6 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"path"
"sort" "sort"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
@ -28,8 +27,8 @@ type target struct {
size int64 size int64
} }
// trustedPush handles content trust pushing of an image // TrustedPush handles content trust pushing of an image
func trustedPush(ctx context.Context, cli command.Cli, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig types.AuthConfig, requestPrivilege types.RequestPrivilegeFunc) error { func TrustedPush(ctx context.Context, cli command.Cli, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig types.AuthConfig, requestPrivilege types.RequestPrivilegeFunc) error {
responseBody, err := imagePushPrivileged(ctx, cli, authConfig, ref, requestPrivilege) responseBody, err := imagePushPrivileged(ctx, cli, authConfig, ref, requestPrivilege)
if err != nil { if err != nil {
return err return err
@ -103,25 +102,25 @@ func PushTrustedReference(streams command.Streams, repoInfo *registry.Repository
fmt.Fprintln(streams.Out(), "Signing and pushing trust metadata") fmt.Fprintln(streams.Out(), "Signing and pushing trust metadata")
repo, err := trust.GetNotaryRepository(streams, repoInfo, authConfig, "push", "pull") repo, err := trust.GetNotaryRepository(streams.In(), streams.Out(), command.UserAgent(), repoInfo, &authConfig, "push", "pull")
if err != nil { if err != nil {
fmt.Fprintf(streams.Out(), "Error establishing connection to notary repository: %s\n", err) fmt.Fprintf(streams.Out(), "Error establishing connection to notary repository: %s\n", err)
return err return err
} }
// get the latest repository metadata so we can figure out which roles to sign // get the latest repository metadata so we can figure out which roles to sign
err = repo.Update(false) _, err = repo.ListTargets()
switch err.(type) { switch err.(type) {
case client.ErrRepoNotInitialized, client.ErrRepositoryNotExist: case client.ErrRepoNotInitialized, client.ErrRepositoryNotExist:
keys := repo.CryptoService.ListKeys(data.CanonicalRootRole) keys := repo.GetCryptoService().ListKeys(data.CanonicalRootRole)
var rootKeyID string var rootKeyID string
// always select the first root key // always select the first root key
if len(keys) > 0 { if len(keys) > 0 {
sort.Strings(keys) sort.Strings(keys)
rootKeyID = keys[0] rootKeyID = keys[0]
} else { } else {
rootPublicKey, err := repo.CryptoService.Create(data.CanonicalRootRole, "", data.ECDSAKey) rootPublicKey, err := repo.GetCryptoService().Create(data.CanonicalRootRole, "", data.ECDSAKey)
if err != nil { if err != nil {
return err return err
} }
@ -136,7 +135,7 @@ func PushTrustedReference(streams command.Streams, repoInfo *registry.Repository
err = repo.AddTarget(target, data.CanonicalTargetsRole) err = repo.AddTarget(target, data.CanonicalTargetsRole)
case nil: case nil:
// already initialized and we have successfully downloaded the latest metadata // already initialized and we have successfully downloaded the latest metadata
err = addTargetToAllSignableRoles(repo, target) err = AddTargetToAllSignableRoles(repo, target)
default: default:
return trust.NotaryError(repoInfo.Name.Name(), err) return trust.NotaryError(repoInfo.Name.Name(), err)
} }
@ -154,51 +153,16 @@ func PushTrustedReference(streams command.Streams, repoInfo *registry.Repository
return nil return nil
} }
// Attempt to add the image target to all the top level delegation roles we can // AddTargetToAllSignableRoles attempts to add the image target to all the top level delegation roles we can
// (based on whether we have the signing key and whether the role's path allows // (based on whether we have the signing key and whether the role's path allows
// us to). // us to).
// If there are no delegation roles, we add to the targets role. // If there are no delegation roles, we add to the targets role.
func addTargetToAllSignableRoles(repo *client.NotaryRepository, target *client.Target) error { func AddTargetToAllSignableRoles(repo client.Repository, target *client.Target) error {
var signableRoles []string signableRoles, err := trust.GetSignableRoles(repo, target)
// translate the full key names, which includes the GUN, into just the key IDs
allCanonicalKeyIDs := make(map[string]struct{})
for fullKeyID := range repo.CryptoService.ListAllKeys() {
allCanonicalKeyIDs[path.Base(fullKeyID)] = struct{}{}
}
allDelegationRoles, err := repo.GetDelegationRoles()
if err != nil { if err != nil {
return err return err
} }
// if there are no delegation roles, then just try to sign it into the targets role
if len(allDelegationRoles) == 0 {
return repo.AddTarget(target, data.CanonicalTargetsRole)
}
// there are delegation roles, find every delegation role we have a key for, and
// attempt to sign into into all those roles.
for _, delegationRole := range allDelegationRoles {
// We do not support signing any delegation role that isn't a direct child of the targets role.
// Also don't bother checking the keys if we can't add the target
// to this role due to path restrictions
if path.Dir(delegationRole.Name) != data.CanonicalTargetsRole || !delegationRole.CheckPaths(target.Name) {
continue
}
for _, canonicalKeyID := range delegationRole.KeyIDs {
if _, ok := allCanonicalKeyIDs[canonicalKeyID]; ok {
signableRoles = append(signableRoles, delegationRole.Name)
break
}
}
}
if len(signableRoles) == 0 {
return errors.Errorf("no valid signing keys for delegation roles")
}
return repo.AddTarget(target, signableRoles...) return repo.AddTarget(target, signableRoles...)
} }
@ -220,7 +184,7 @@ func imagePushPrivileged(ctx context.Context, cli command.Cli, authConfig types.
func trustedPull(ctx context.Context, cli command.Cli, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig types.AuthConfig, requestPrivilege types.RequestPrivilegeFunc) error { func trustedPull(ctx context.Context, cli command.Cli, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig types.AuthConfig, requestPrivilege types.RequestPrivilegeFunc) error {
var refs []target var refs []target
notaryRepo, err := trust.GetNotaryRepository(cli, repoInfo, authConfig, "pull") notaryRepo, err := trust.GetNotaryRepository(cli.In(), cli.Out(), command.UserAgent(), repoInfo, &authConfig, "pull")
if err != nil { if err != nil {
fmt.Fprintf(cli.Out(), "Error establishing connection to trust repository: %s\n", err) fmt.Fprintf(cli.Out(), "Error establishing connection to trust repository: %s\n", err)
return err return err
@ -335,7 +299,7 @@ func TrustedReference(ctx context.Context, cli command.Cli, ref reference.NamedT
// Resolve the Auth config relevant for this server // Resolve the Auth config relevant for this server
authConfig := command.ResolveAuthConfig(ctx, cli, repoInfo.Index) authConfig := command.ResolveAuthConfig(ctx, cli, repoInfo.Index)
notaryRepo, err := trust.GetNotaryRepository(cli, repoInfo, authConfig, "pull") notaryRepo, err := trust.GetNotaryRepository(cli.In(), cli.Out(), command.UserAgent(), repoInfo, &authConfig, "pull")
if err != nil { if err != nil {
fmt.Fprintf(cli.Out(), "Error establishing connection to trust repository: %s\n", err) fmt.Fprintf(cli.Out(), "Error establishing connection to trust repository: %s\n", err)
return nil, err return nil, err
@ -348,7 +312,7 @@ func TrustedReference(ctx context.Context, cli command.Cli, ref reference.NamedT
// Only list tags in the top level targets role or the releases delegation role - ignore // Only list tags in the top level targets role or the releases delegation role - ignore
// all other delegation roles // all other delegation roles
if t.Role != trust.ReleasesRole && t.Role != data.CanonicalTargetsRole { if t.Role != trust.ReleasesRole && t.Role != data.CanonicalTargetsRole {
return nil, trust.NotaryError(repoInfo.Name.Name(), errors.Errorf("No trust data for %s", ref.Tag())) return nil, trust.NotaryError(repoInfo.Name.Name(), client.ErrNoSuchTarget(ref.Tag()))
} }
r, err := convertTarget(t.Target) r, err := convertTarget(t.Target)
if err != nil { if err != nil {

View File

@ -1,12 +1,17 @@
package image package image
import ( import (
"io/ioutil"
"os" "os"
"testing" "testing"
"github.com/docker/cli/cli/trust" "github.com/docker/cli/cli/trust"
registrytypes "github.com/docker/docker/api/types/registry" registrytypes "github.com/docker/docker/api/types/registry"
"github.com/docker/docker/registry" "github.com/docker/docker/registry"
"github.com/docker/notary/client"
"github.com/docker/notary/passphrase"
"github.com/docker/notary/trustpinning"
"github.com/stretchr/testify/assert"
) )
func unsetENV() { func unsetENV() {
@ -55,3 +60,14 @@ func TestNonOfficialTrustServer(t *testing.T) {
t.Fatalf("Expected server to be %s, got %s", expectedStr, output) t.Fatalf("Expected server to be %s, got %s", expectedStr, output)
} }
} }
func TestAddTargetToAllSignableRolesError(t *testing.T) {
tmpDir, err := ioutil.TempDir("", "notary-test-")
assert.NoError(t, err)
defer os.RemoveAll(tmpDir)
notaryRepo, err := client.NewFileCachedRepository(tmpDir, "gun", "https://localhost", nil, passphrase.ConstantRetriever("password"), trustpinning.TrustPinConfig{})
target := client.Target{}
err = AddTargetToAllSignableRoles(notaryRepo, &target)
assert.EqualError(t, err, "client is offline")
}

View File

@ -59,7 +59,7 @@ func trustedResolveDigest(ctx context.Context, cli command.Cli, ref reference.Na
authConfig := command.ResolveAuthConfig(ctx, cli, repoInfo.Index) authConfig := command.ResolveAuthConfig(ctx, cli, repoInfo.Index)
notaryRepo, err := trust.GetNotaryRepository(cli, repoInfo, authConfig, "pull") notaryRepo, err := trust.GetNotaryRepository(cli.In(), cli.Out(), command.UserAgent(), repoInfo, &authConfig, "pull")
if err != nil { if err != nil {
return nil, errors.Wrap(err, "error establishing connection to trust repository") return nil, errors.Wrap(err, "error establishing connection to trust repository")
} }

View File

@ -0,0 +1,413 @@
package trust
import (
"github.com/docker/cli/cli/trust"
"github.com/docker/notary/client"
"github.com/docker/notary/client/changelist"
"github.com/docker/notary/cryptoservice"
"github.com/docker/notary/passphrase"
"github.com/docker/notary/storage"
"github.com/docker/notary/trustmanager"
"github.com/docker/notary/tuf/data"
"github.com/docker/notary/tuf/signed"
)
// Sample mock CLI interfaces
func getOfflineNotaryRepository(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (client.Repository, error) {
return OfflineNotaryRepository{}, nil
}
// OfflineNotaryRepository is a mock Notary repository that is offline
type OfflineNotaryRepository struct{}
func (o OfflineNotaryRepository) Initialize(rootKeyIDs []string, serverManagedRoles ...data.RoleName) error {
return storage.ErrOffline{}
}
func (o OfflineNotaryRepository) InitializeWithCertificate(rootKeyIDs []string, rootCerts []data.PublicKey, serverManagedRoles ...data.RoleName) error {
return storage.ErrOffline{}
}
func (o OfflineNotaryRepository) Publish() error {
return storage.ErrOffline{}
}
func (o OfflineNotaryRepository) AddTarget(target *client.Target, roles ...data.RoleName) error {
return nil
}
func (o OfflineNotaryRepository) RemoveTarget(targetName string, roles ...data.RoleName) error {
return nil
}
func (o OfflineNotaryRepository) ListTargets(roles ...data.RoleName) ([]*client.TargetWithRole, error) {
return nil, storage.ErrOffline{}
}
func (o OfflineNotaryRepository) GetTargetByName(name string, roles ...data.RoleName) (*client.TargetWithRole, error) {
return nil, storage.ErrOffline{}
}
func (o OfflineNotaryRepository) GetAllTargetMetadataByName(name string) ([]client.TargetSignedStruct, error) {
return nil, storage.ErrOffline{}
}
func (o OfflineNotaryRepository) GetChangelist() (changelist.Changelist, error) {
return changelist.NewMemChangelist(), nil
}
func (o OfflineNotaryRepository) ListRoles() ([]client.RoleWithSignatures, error) {
return nil, storage.ErrOffline{}
}
func (o OfflineNotaryRepository) GetDelegationRoles() ([]data.Role, error) {
return nil, storage.ErrOffline{}
}
func (o OfflineNotaryRepository) AddDelegation(name data.RoleName, delegationKeys []data.PublicKey, paths []string) error {
return nil
}
func (o OfflineNotaryRepository) AddDelegationRoleAndKeys(name data.RoleName, delegationKeys []data.PublicKey) error {
return nil
}
func (o OfflineNotaryRepository) AddDelegationPaths(name data.RoleName, paths []string) error {
return nil
}
func (o OfflineNotaryRepository) RemoveDelegationKeysAndPaths(name data.RoleName, keyIDs, paths []string) error {
return nil
}
func (o OfflineNotaryRepository) RemoveDelegationRole(name data.RoleName) error {
return nil
}
func (o OfflineNotaryRepository) RemoveDelegationPaths(name data.RoleName, paths []string) error {
return nil
}
func (o OfflineNotaryRepository) RemoveDelegationKeys(name data.RoleName, keyIDs []string) error {
return nil
}
func (o OfflineNotaryRepository) ClearDelegationPaths(name data.RoleName) error {
return nil
}
func (o OfflineNotaryRepository) Witness(roles ...data.RoleName) ([]data.RoleName, error) {
return nil, nil
}
func (o OfflineNotaryRepository) RotateKey(role data.RoleName, serverManagesKey bool, keyList []string) error {
return storage.ErrOffline{}
}
func (o OfflineNotaryRepository) GetCryptoService() signed.CryptoService {
return nil
}
func (o OfflineNotaryRepository) SetLegacyVersions(version int) {}
func (o OfflineNotaryRepository) GetGUN() data.GUN {
return data.GUN("gun")
}
func getUninitializedNotaryRepository(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (client.Repository, error) {
return UninitializedNotaryRepository{}, nil
}
// UninitializedNotaryRepository is a mock Notary repository that is uninintialized
// it builds on top of the OfflineNotaryRepository, instead returning ErrRepositoryNotExist
// for any online operation
type UninitializedNotaryRepository struct {
OfflineNotaryRepository
}
func (u UninitializedNotaryRepository) Initialize(rootKeyIDs []string, serverManagedRoles ...data.RoleName) error {
return client.ErrRepositoryNotExist{}
}
func (u UninitializedNotaryRepository) InitializeWithCertificate(rootKeyIDs []string, rootCerts []data.PublicKey, serverManagedRoles ...data.RoleName) error {
return client.ErrRepositoryNotExist{}
}
func (u UninitializedNotaryRepository) Publish() error {
return client.ErrRepositoryNotExist{}
}
func (u UninitializedNotaryRepository) ListTargets(roles ...data.RoleName) ([]*client.TargetWithRole, error) {
return nil, client.ErrRepositoryNotExist{}
}
func (u UninitializedNotaryRepository) GetTargetByName(name string, roles ...data.RoleName) (*client.TargetWithRole, error) {
return nil, client.ErrRepositoryNotExist{}
}
func (u UninitializedNotaryRepository) GetAllTargetMetadataByName(name string) ([]client.TargetSignedStruct, error) {
return nil, client.ErrRepositoryNotExist{}
}
func (u UninitializedNotaryRepository) ListRoles() ([]client.RoleWithSignatures, error) {
return nil, client.ErrRepositoryNotExist{}
}
func (u UninitializedNotaryRepository) GetDelegationRoles() ([]data.Role, error) {
return nil, client.ErrRepositoryNotExist{}
}
func (u UninitializedNotaryRepository) RotateKey(role data.RoleName, serverManagesKey bool, keyList []string) error {
return client.ErrRepositoryNotExist{}
}
func getEmptyTargetsNotaryRepository(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (client.Repository, error) {
return EmptyTargetsNotaryRepository{}, nil
}
// EmptyTargetsNotaryRepository is a mock Notary repository that is initialized
// but does not have any signed targets
type EmptyTargetsNotaryRepository struct {
OfflineNotaryRepository
}
func (e EmptyTargetsNotaryRepository) Initialize(rootKeyIDs []string, serverManagedRoles ...data.RoleName) error {
return nil
}
func (e EmptyTargetsNotaryRepository) InitializeWithCertificate(rootKeyIDs []string, rootCerts []data.PublicKey, serverManagedRoles ...data.RoleName) error {
return nil
}
func (e EmptyTargetsNotaryRepository) Publish() error {
return nil
}
func (e EmptyTargetsNotaryRepository) ListTargets(roles ...data.RoleName) ([]*client.TargetWithRole, error) {
return []*client.TargetWithRole{}, nil
}
func (e EmptyTargetsNotaryRepository) GetTargetByName(name string, roles ...data.RoleName) (*client.TargetWithRole, error) {
return nil, client.ErrNoSuchTarget(name)
}
func (e EmptyTargetsNotaryRepository) GetAllTargetMetadataByName(name string) ([]client.TargetSignedStruct, error) {
return nil, client.ErrNoSuchTarget(name)
}
func (e EmptyTargetsNotaryRepository) ListRoles() ([]client.RoleWithSignatures, error) {
return []client.RoleWithSignatures{}, nil
}
func (e EmptyTargetsNotaryRepository) GetDelegationRoles() ([]data.Role, error) {
return []data.Role{}, nil
}
func (e EmptyTargetsNotaryRepository) RotateKey(role data.RoleName, serverManagesKey bool, keyList []string) error {
return nil
}
func getLoadedNotaryRepository(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (client.Repository, error) {
return LoadedNotaryRepository{}, nil
}
// LoadedNotaryRepository is a mock Notary repository that is loaded with targets, delegations, and keys
type LoadedNotaryRepository struct {
EmptyTargetsNotaryRepository
statefulCryptoService signed.CryptoService
}
// LoadedNotaryRepository has three delegations:
// - targets/releases: includes keys A and B
// - targets/alice: includes key A
// - targets/bob: includes key B
var loadedReleasesRole = data.DelegationRole{
BaseRole: data.BaseRole{
Name: "targets/releases",
Keys: map[string]data.PublicKey{"A": nil, "B": nil},
Threshold: 1,
},
}
var loadedAliceRole = data.DelegationRole{
BaseRole: data.BaseRole{
Name: "targets/alice",
Keys: map[string]data.PublicKey{"A": nil},
Threshold: 1,
},
}
var loadedBobRole = data.DelegationRole{
BaseRole: data.BaseRole{
Name: "targets/bob",
Keys: map[string]data.PublicKey{"B": nil},
Threshold: 1,
},
}
var loadedDelegationRoles = []data.Role{
{
Name: loadedReleasesRole.Name,
RootRole: data.RootRole{
KeyIDs: []string{"A", "B"},
Threshold: 1,
},
},
{
Name: loadedAliceRole.Name,
RootRole: data.RootRole{
KeyIDs: []string{"A"},
Threshold: 1,
},
},
{
Name: loadedBobRole.Name,
RootRole: data.RootRole{
KeyIDs: []string{"B"},
Threshold: 1,
},
},
}
var loadedTargetsRole = data.DelegationRole{
BaseRole: data.BaseRole{
Name: data.CanonicalTargetsRole,
Keys: map[string]data.PublicKey{"C": nil},
Threshold: 1,
},
}
// LoadedNotaryRepository has three targets:
// - red: signed by targets/releases, targets/alice, targets/bob
// - blue: signed by targets/releases, targets/alice
// - green: signed by targets/releases
var loadedRedTarget = client.Target{
Name: "red",
Hashes: data.Hashes{"sha256": []byte("red-digest")},
}
var loadedBlueTarget = client.Target{
Name: "blue",
Hashes: data.Hashes{"sha256": []byte("blue-digest")},
}
var loadedGreenTarget = client.Target{
Name: "green",
Hashes: data.Hashes{"sha256": []byte("green-digest")},
}
var loadedTargets = []client.TargetSignedStruct{
// red is signed by all three delegations
{Target: loadedRedTarget, Role: loadedReleasesRole},
{Target: loadedRedTarget, Role: loadedAliceRole},
{Target: loadedRedTarget, Role: loadedBobRole},
// blue is signed by targets/releases, targets/alice
{Target: loadedBlueTarget, Role: loadedReleasesRole},
{Target: loadedBlueTarget, Role: loadedAliceRole},
// green is signed by targets/releases
{Target: loadedGreenTarget, Role: loadedReleasesRole},
}
func (l LoadedNotaryRepository) ListRoles() ([]client.RoleWithSignatures, error) {
rootRole := data.Role{
RootRole: data.RootRole{
KeyIDs: []string{"rootID"},
Threshold: 1,
},
Name: data.CanonicalRootRole,
}
targetsRole := data.Role{
RootRole: data.RootRole{
KeyIDs: []string{"targetsID"},
Threshold: 1,
},
Name: data.CanonicalTargetsRole,
}
return []client.RoleWithSignatures{{Role: rootRole}, {Role: targetsRole}}, nil
}
func (l LoadedNotaryRepository) ListTargets(roles ...data.RoleName) ([]*client.TargetWithRole, error) {
filteredTargets := []*client.TargetWithRole{}
for _, tgt := range loadedTargets {
if len(roles) == 0 || (len(roles) > 0 && roles[0] == tgt.Role.Name) {
filteredTargets = append(filteredTargets, &client.TargetWithRole{Target: tgt.Target, Role: tgt.Role.Name})
}
}
return filteredTargets, nil
}
func (l LoadedNotaryRepository) GetTargetByName(name string, roles ...data.RoleName) (*client.TargetWithRole, error) {
for _, tgt := range loadedTargets {
if name == tgt.Target.Name {
if len(roles) == 0 || (len(roles) > 0 && roles[0] == tgt.Role.Name) {
return &client.TargetWithRole{Target: tgt.Target, Role: tgt.Role.Name}, nil
}
}
}
return nil, client.ErrNoSuchTarget(name)
}
func (l LoadedNotaryRepository) GetAllTargetMetadataByName(name string) ([]client.TargetSignedStruct, error) {
if name == "" {
return loadedTargets, nil
}
filteredTargets := []client.TargetSignedStruct{}
for _, tgt := range loadedTargets {
if name == tgt.Target.Name {
filteredTargets = append(filteredTargets, tgt)
}
}
if len(filteredTargets) == 0 {
return nil, client.ErrNoSuchTarget(name)
}
return filteredTargets, nil
}
func (l LoadedNotaryRepository) GetGUN() data.GUN {
return data.GUN("signed-repo")
}
func (l LoadedNotaryRepository) GetDelegationRoles() ([]data.Role, error) {
return loadedDelegationRoles, nil
}
func (l LoadedNotaryRepository) GetCryptoService() signed.CryptoService {
if l.statefulCryptoService == nil {
// give it an in-memory cryptoservice with a root key and targets key
l.statefulCryptoService = cryptoservice.NewCryptoService(trustmanager.NewKeyMemoryStore(passphrase.ConstantRetriever("password")))
l.statefulCryptoService.AddKey(data.CanonicalRootRole, l.GetGUN(), nil)
l.statefulCryptoService.AddKey(data.CanonicalTargetsRole, l.GetGUN(), nil)
}
return l.statefulCryptoService
}
func getLoadedWithNoSignersNotaryRepository(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (client.Repository, error) {
return LoadedWithNoSignersNotaryRepository{}, nil
}
// LoadedWithNoSignersNotaryRepository is a mock Notary repository that is loaded with targets but no delegations
// it only contains the green target
type LoadedWithNoSignersNotaryRepository struct {
LoadedNotaryRepository
}
func (l LoadedWithNoSignersNotaryRepository) ListTargets(roles ...data.RoleName) ([]*client.TargetWithRole, error) {
filteredTargets := []*client.TargetWithRole{}
for _, tgt := range loadedTargets {
if len(roles) == 0 || (len(roles) > 0 && roles[0] == tgt.Role.Name) {
filteredTargets = append(filteredTargets, &client.TargetWithRole{Target: tgt.Target, Role: tgt.Role.Name})
}
}
return filteredTargets, nil
}
func (l LoadedWithNoSignersNotaryRepository) GetTargetByName(name string, roles ...data.RoleName) (*client.TargetWithRole, error) {
if name == "" || name == loadedGreenTarget.Name {
return &client.TargetWithRole{Target: loadedGreenTarget, Role: data.CanonicalTargetsRole}, nil
}
return nil, client.ErrNoSuchTarget(name)
}
func (l LoadedWithNoSignersNotaryRepository) GetAllTargetMetadataByName(name string) ([]client.TargetSignedStruct, error) {
if name == "" || name == loadedGreenTarget.Name {
return []client.TargetSignedStruct{{Target: loadedGreenTarget, Role: loadedTargetsRole}}, nil
}
return nil, client.ErrNoSuchTarget(name)
}
func (l LoadedWithNoSignersNotaryRepository) GetDelegationRoles() ([]data.Role, error) {
return []data.Role{}, nil
}

23
cli/command/trust/cmd.go Normal file
View File

@ -0,0 +1,23 @@
package trust
import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/spf13/cobra"
)
// NewTrustCommand returns a cobra command for `trust` subcommands
func NewTrustCommand(dockerCli command.Cli) *cobra.Command {
cmd := &cobra.Command{
Use: "trust",
Short: "Manage trust on Docker images (experimental)",
Args: cli.NoArgs,
RunE: command.ShowHelp(dockerCli.Err()),
}
cmd.AddCommand(
newViewCommand(dockerCli),
newRevokeCommand(dockerCli),
newSignCommand(dockerCli),
)
return cmd
}

View File

@ -0,0 +1,33 @@
package trust
import (
"strings"
"github.com/docker/cli/cli/trust"
"github.com/docker/notary/client"
"github.com/docker/notary/tuf/data"
)
const releasedRoleName = "Repo Admin"
// check if a role name is "released": either targets/releases or targets TUF roles
func isReleasedTarget(role data.RoleName) bool {
return role == data.CanonicalTargetsRole || role == trust.ReleasesRole
}
// convert TUF role name to a human-understandable signer name
func notaryRoleToSigner(tufRole data.RoleName) string {
// don't show a signer for "targets" or "targets/releases"
if isReleasedTarget(data.RoleName(tufRole.String())) {
return releasedRoleName
}
return strings.TrimPrefix(tufRole.String(), "targets/")
}
func clearChangeList(notaryRepo client.Repository) error {
cl, err := notaryRepo.GetChangelist()
if err != nil {
return err
}
return cl.Clear("")
}

130
cli/command/trust/revoke.go Normal file
View File

@ -0,0 +1,130 @@
package trust
import (
"context"
"fmt"
"os"
"github.com/docker/docker/api/types"
registrytypes "github.com/docker/docker/api/types/registry"
"github.com/docker/notary/client"
"github.com/docker/notary/tuf/data"
"github.com/pkg/errors"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/trust"
"github.com/spf13/cobra"
)
type revokeOptions struct {
forceYes bool
}
func newRevokeCommand(dockerCli command.Cli) *cobra.Command {
options := revokeOptions{}
cmd := &cobra.Command{
Use: "revoke [OPTIONS] IMAGE[:TAG]",
Short: "Remove trust for an image",
Args: cli.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return revokeTrust(dockerCli, args[0], options)
},
}
flags := cmd.Flags()
flags.BoolVarP(&options.forceYes, "yes", "y", false, "Do not prompt for confirmation")
return cmd
}
func revokeTrust(cli command.Cli, remote string, options revokeOptions) error {
ctx := context.Background()
authResolver := func(ctx context.Context, index *registrytypes.IndexInfo) types.AuthConfig {
return command.ResolveAuthConfig(ctx, cli, index)
}
imgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, authResolver, remote)
if err != nil {
return err
}
tag := imgRefAndAuth.Tag()
if imgRefAndAuth.Tag() == "" && imgRefAndAuth.Digest() != "" {
return fmt.Errorf("cannot use a digest reference for IMAGE:TAG")
}
if imgRefAndAuth.Tag() == "" && !options.forceYes {
deleteRemote := command.PromptForConfirmation(os.Stdin, cli.Out(), fmt.Sprintf("Please confirm you would like to delete all signature data for %s?", remote))
if !deleteRemote {
fmt.Fprintf(cli.Out(), "\nAborting action.\n")
return nil
}
}
notaryRepo, err := cli.NotaryClient(*imgRefAndAuth, trust.ActionsPushAndPull)
if err != nil {
return err
}
if err = clearChangeList(notaryRepo); err != nil {
return err
}
defer clearChangeList(notaryRepo)
if err := revokeSignature(notaryRepo, tag); err != nil {
return errors.Wrapf(err, "could not remove signature for %s", remote)
}
fmt.Fprintf(cli.Out(), "Successfully deleted signature for %s\n", remote)
return nil
}
func revokeSignature(notaryRepo client.Repository, tag string) error {
if tag != "" {
// Revoke signature for the specified tag
if err := revokeSingleSig(notaryRepo, tag); err != nil {
return err
}
} else {
// revoke all signatures for the image, as no tag was given
if err := revokeAllSigs(notaryRepo); err != nil {
return err
}
}
// Publish change
return notaryRepo.Publish()
}
func revokeSingleSig(notaryRepo client.Repository, tag string) error {
releasedTargetWithRole, err := notaryRepo.GetTargetByName(tag, trust.ReleasesRole, data.CanonicalTargetsRole)
if err != nil {
return err
}
releasedTarget := releasedTargetWithRole.Target
return getSignableRolesForTargetAndRemove(releasedTarget, notaryRepo)
}
func revokeAllSigs(notaryRepo client.Repository) error {
releasedTargetWithRoleList, err := notaryRepo.ListTargets(trust.ReleasesRole, data.CanonicalTargetsRole)
if err != nil {
return err
}
if len(releasedTargetWithRoleList) == 0 {
return fmt.Errorf("no signed tags to remove")
}
// we need all the roles that signed each released target so we can remove from all roles.
for _, releasedTargetWithRole := range releasedTargetWithRoleList {
// remove from all roles
if err := getSignableRolesForTargetAndRemove(releasedTargetWithRole.Target, notaryRepo); err != nil {
return err
}
}
return nil
}
// get all the roles that signed the target and removes it from all roles.
func getSignableRolesForTargetAndRemove(releasedTarget client.Target, notaryRepo client.Repository) error {
signableRoles, err := trust.GetSignableRoles(notaryRepo, &releasedTarget)
if err != nil {
return err
}
// remove from all roles
return notaryRepo.RemoveTarget(releasedTarget.Name, signableRoles...)
}

View File

@ -0,0 +1,146 @@
package trust
import (
"io/ioutil"
"os"
"testing"
"github.com/docker/cli/internal/test"
"github.com/docker/cli/internal/test/testutil"
"github.com/docker/notary/client"
"github.com/docker/notary/passphrase"
"github.com/docker/notary/trustpinning"
"github.com/stretchr/testify/assert"
)
func TestTrustRevokeCommandErrors(t *testing.T) {
testCases := []struct {
name string
args []string
expectedError string
}{
{
name: "not-enough-args",
expectedError: "requires exactly 1 argument",
},
{
name: "too-many-args",
args: []string{"remote1", "remote2"},
expectedError: "requires exactly 1 argument",
},
{
name: "sha-reference",
args: []string{"870d292919d01a0af7e7f056271dc78792c05f55f49b9b9012b6d89725bd9abd"},
expectedError: "invalid repository name",
},
{
name: "invalid-img-reference",
args: []string{"ALPINE"},
expectedError: "invalid reference format",
},
{
name: "digest-reference",
args: []string{"ubuntu@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2"},
expectedError: "cannot use a digest reference for IMAGE:TAG",
},
}
for _, tc := range testCases {
cmd := newRevokeCommand(
test.NewFakeCli(&fakeClient{}))
cmd.SetArgs(tc.args)
cmd.SetOutput(ioutil.Discard)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}
func TestTrustRevokeCommandOfflineErrors(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getOfflineNotaryRepository)
cmd := newRevokeCommand(cli)
cmd.SetArgs([]string{"reg-name.io/image"})
cmd.SetOutput(ioutil.Discard)
assert.NoError(t, cmd.Execute())
assert.Contains(t, cli.OutBuffer().String(), "Please confirm you would like to delete all signature data for reg-name.io/image? [y/N] \nAborting action.")
cli = test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getOfflineNotaryRepository)
cmd = newRevokeCommand(cli)
cmd.SetArgs([]string{"reg-name.io/image", "-y"})
cmd.SetOutput(ioutil.Discard)
cli = test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getOfflineNotaryRepository)
cmd = newRevokeCommand(cli)
cmd.SetArgs([]string{"reg-name.io/image:tag"})
cmd.SetOutput(ioutil.Discard)
testutil.ErrorContains(t, cmd.Execute(), "could not remove signature for reg-name.io/image:tag: client is offline")
}
func TestTrustRevokeCommandUninitializedErrors(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getUninitializedNotaryRepository)
cmd := newRevokeCommand(cli)
cmd.SetArgs([]string{"reg-name.io/image"})
cmd.SetOutput(ioutil.Discard)
assert.NoError(t, cmd.Execute())
assert.Contains(t, cli.OutBuffer().String(), "Please confirm you would like to delete all signature data for reg-name.io/image? [y/N] \nAborting action.")
cli = test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getUninitializedNotaryRepository)
cmd = newRevokeCommand(cli)
cmd.SetArgs([]string{"reg-name.io/image", "-y"})
cmd.SetOutput(ioutil.Discard)
testutil.ErrorContains(t, cmd.Execute(), "could not remove signature for reg-name.io/image: does not have trust data for")
cli = test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getUninitializedNotaryRepository)
cmd = newRevokeCommand(cli)
cmd.SetArgs([]string{"reg-name.io/image:tag"})
cmd.SetOutput(ioutil.Discard)
testutil.ErrorContains(t, cmd.Execute(), "could not remove signature for reg-name.io/image:tag: does not have trust data for")
}
func TestTrustRevokeCommandEmptyNotaryRepo(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getEmptyTargetsNotaryRepository)
cmd := newRevokeCommand(cli)
cmd.SetArgs([]string{"reg-name.io/image"})
cmd.SetOutput(ioutil.Discard)
assert.NoError(t, cmd.Execute())
assert.Contains(t, cli.OutBuffer().String(), "Please confirm you would like to delete all signature data for reg-name.io/image? [y/N] \nAborting action.")
cli = test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getEmptyTargetsNotaryRepository)
cmd = newRevokeCommand(cli)
cmd.SetArgs([]string{"reg-name.io/image", "-y"})
cmd.SetOutput(ioutil.Discard)
testutil.ErrorContains(t, cmd.Execute(), "could not remove signature for reg-name.io/image: no signed tags to remove")
cli = test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getEmptyTargetsNotaryRepository)
cmd = newRevokeCommand(cli)
cmd.SetArgs([]string{"reg-name.io/image:tag"})
cmd.SetOutput(ioutil.Discard)
testutil.ErrorContains(t, cmd.Execute(), "could not remove signature for reg-name.io/image:tag: No valid trust data for tag")
}
func TestNewRevokeTrustAllSigConfirmation(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getEmptyTargetsNotaryRepository)
cmd := newRevokeCommand(cli)
cmd.SetArgs([]string{"alpine"})
assert.NoError(t, cmd.Execute())
assert.Contains(t, cli.OutBuffer().String(), "Please confirm you would like to delete all signature data for alpine? [y/N] \nAborting action.")
}
func TestGetSignableRolesForTargetAndRemoveError(t *testing.T) {
tmpDir, err := ioutil.TempDir("", "notary-test-")
assert.NoError(t, err)
defer os.RemoveAll(tmpDir)
notaryRepo, err := client.NewFileCachedRepository(tmpDir, "gun", "https://localhost", nil, passphrase.ConstantRetriever("password"), trustpinning.TrustPinConfig{})
target := client.Target{}
err = getSignableRolesForTargetAndRemove(target, notaryRepo)
assert.EqualError(t, err, "client is offline")
}

220
cli/command/trust/sign.go Normal file
View File

@ -0,0 +1,220 @@
package trust
import (
"context"
"fmt"
"path"
"sort"
"strings"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/image"
"github.com/docker/cli/cli/trust"
"github.com/docker/docker/api/types"
registrytypes "github.com/docker/docker/api/types/registry"
"github.com/docker/notary/client"
"github.com/docker/notary/tuf/data"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
func newSignCommand(dockerCli command.Cli) *cobra.Command {
cmd := &cobra.Command{
Use: "sign [OPTIONS] IMAGE:TAG",
Short: "Sign an image",
Args: cli.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return signImage(dockerCli, args[0])
},
}
return cmd
}
func signImage(cli command.Cli, imageName string) error {
ctx := context.Background()
authResolver := func(ctx context.Context, index *registrytypes.IndexInfo) types.AuthConfig {
return command.ResolveAuthConfig(ctx, cli, index)
}
imgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, authResolver, imageName)
if err != nil {
return err
}
tag := imgRefAndAuth.Tag()
if tag == "" {
if imgRefAndAuth.Digest() != "" {
return fmt.Errorf("cannot use a digest reference for IMAGE:TAG")
}
return fmt.Errorf("No tag specified for %s", imageName)
}
notaryRepo, err := cli.NotaryClient(*imgRefAndAuth, trust.ActionsPushAndPull)
if err != nil {
return trust.NotaryError(imgRefAndAuth.Reference().Name(), err)
}
if err = clearChangeList(notaryRepo); err != nil {
return err
}
defer clearChangeList(notaryRepo)
// get the latest repository metadata so we can figure out which roles to sign
if _, err = notaryRepo.ListTargets(); err != nil {
switch err.(type) {
case client.ErrRepoNotInitialized, client.ErrRepositoryNotExist:
// before initializing a new repo, check that the image exists locally:
if err := checkLocalImageExistence(ctx, cli, imageName); err != nil {
return err
}
userRole := data.RoleName(path.Join(data.CanonicalTargetsRole.String(), imgRefAndAuth.AuthConfig().Username))
if err := initNotaryRepoWithSigners(notaryRepo, userRole); err != nil {
return trust.NotaryError(imgRefAndAuth.Reference().Name(), err)
}
fmt.Fprintf(cli.Out(), "Created signer: %s\n", imgRefAndAuth.AuthConfig().Username)
fmt.Fprintf(cli.Out(), "Finished initializing signed repository for %s\n", imageName)
default:
return trust.NotaryError(imgRefAndAuth.RepoInfo().Name.Name(), err)
}
}
requestPrivilege := command.RegistryAuthenticationPrivilegedFunc(cli, imgRefAndAuth.RepoInfo().Index, "push")
target, err := createTarget(notaryRepo, tag)
if err != nil {
switch err := err.(type) {
case client.ErrNoSuchTarget, client.ErrRepositoryNotExist:
// Fail fast if the image doesn't exist locally
if err := checkLocalImageExistence(ctx, cli, imageName); err != nil {
return err
}
return image.TrustedPush(ctx, cli, imgRefAndAuth.RepoInfo(), imgRefAndAuth.Reference(), *imgRefAndAuth.AuthConfig(), requestPrivilege)
default:
return err
}
}
fmt.Fprintf(cli.Out(), "Signing and pushing trust metadata for %s\n", imageName)
existingSigInfo, err := getExistingSignatureInfoForReleasedTag(notaryRepo, tag)
if err != nil {
return err
}
err = image.AddTargetToAllSignableRoles(notaryRepo, &target)
if err == nil {
prettyPrintExistingSignatureInfo(cli, existingSigInfo)
err = notaryRepo.Publish()
}
if err != nil {
return errors.Wrapf(err, "failed to sign %q:%s", imgRefAndAuth.RepoInfo().Name.Name(), tag)
}
fmt.Fprintf(cli.Out(), "Successfully signed %q:%s\n", imgRefAndAuth.RepoInfo().Name.Name(), tag)
return nil
}
func checkLocalImageExistence(ctx context.Context, cli command.Cli, imageName string) error {
_, _, err := cli.Client().ImageInspectWithRaw(ctx, imageName)
return err
}
func createTarget(notaryRepo client.Repository, tag string) (client.Target, error) {
target := &client.Target{}
var err error
if tag == "" {
return *target, fmt.Errorf("No tag specified")
}
target.Name = tag
target.Hashes, target.Length, err = getSignedManifestHashAndSize(notaryRepo, tag)
return *target, err
}
func getSignedManifestHashAndSize(notaryRepo client.Repository, tag string) (data.Hashes, int64, error) {
targets, err := notaryRepo.GetAllTargetMetadataByName(tag)
if err != nil {
return nil, 0, err
}
return getReleasedTargetHashAndSize(targets, tag)
}
func getReleasedTargetHashAndSize(targets []client.TargetSignedStruct, tag string) (data.Hashes, int64, error) {
for _, tgt := range targets {
if isReleasedTarget(tgt.Role.Name) {
return tgt.Target.Hashes, tgt.Target.Length, nil
}
}
return nil, 0, client.ErrNoSuchTarget(tag)
}
func getExistingSignatureInfoForReleasedTag(notaryRepo client.Repository, tag string) (trustTagRow, error) {
targets, err := notaryRepo.GetAllTargetMetadataByName(tag)
if err != nil {
return trustTagRow{}, err
}
releasedTargetInfoList := matchReleasedSignatures(targets)
if len(releasedTargetInfoList) == 0 {
return trustTagRow{}, nil
}
return releasedTargetInfoList[0], nil
}
func prettyPrintExistingSignatureInfo(cli command.Cli, existingSigInfo trustTagRow) {
sort.Strings(existingSigInfo.Signers)
joinedSigners := strings.Join(existingSigInfo.Signers, ", ")
fmt.Fprintf(cli.Out(), "Existing signatures for tag %s digest %s from:\n%s\n", existingSigInfo.TagName, existingSigInfo.HashHex, joinedSigners)
}
func initNotaryRepoWithSigners(notaryRepo client.Repository, newSigner data.RoleName) error {
rootKey, err := getOrGenerateNotaryKey(notaryRepo, data.CanonicalRootRole)
if err != nil {
return err
}
rootKeyID := rootKey.ID()
// Initialize the notary repository with a remotely managed snapshot key
if err := notaryRepo.Initialize([]string{rootKeyID}, data.CanonicalSnapshotRole); err != nil {
return err
}
signerKey, err := getOrGenerateNotaryKey(notaryRepo, newSigner)
if err != nil {
return err
}
addStagedSigner(notaryRepo, newSigner, []data.PublicKey{signerKey})
return notaryRepo.Publish()
}
// generates an ECDSA key without a GUN for the specified role
func getOrGenerateNotaryKey(notaryRepo client.Repository, role data.RoleName) (data.PublicKey, error) {
// use the signer name in the PEM headers if this is a delegation key
if data.IsDelegation(role) {
role = data.RoleName(notaryRoleToSigner(role))
}
keys := notaryRepo.GetCryptoService().ListKeys(role)
var err error
var key data.PublicKey
// always select the first key by ID
if len(keys) > 0 {
sort.Strings(keys)
keyID := keys[0]
privKey, _, err := notaryRepo.GetCryptoService().GetPrivateKey(keyID)
if err != nil {
return nil, err
}
key = data.PublicKeyFromPrivate(privKey)
} else {
key, err = notaryRepo.GetCryptoService().Create(role, "", data.ECDSAKey)
if err != nil {
return nil, err
}
}
return key, nil
}
// stages changes to add a signer with the specified name and key(s). Adds to targets/<name> and targets/releases
func addStagedSigner(notaryRepo client.Repository, newSigner data.RoleName, signerKeys []data.PublicKey) {
// create targets/<username>
notaryRepo.AddDelegationRoleAndKeys(newSigner, signerKeys)
notaryRepo.AddDelegationPaths(newSigner, []string{""})
// create targets/releases
notaryRepo.AddDelegationRoleAndKeys(trust.ReleasesRole, signerKeys)
notaryRepo.AddDelegationPaths(trust.ReleasesRole, []string{""})
}

View File

@ -0,0 +1,285 @@
package trust
import (
"encoding/json"
"io/ioutil"
"os"
"testing"
"github.com/docker/cli/cli/config"
"github.com/docker/cli/cli/trust"
"github.com/docker/cli/internal/test"
"github.com/docker/cli/internal/test/testutil"
"github.com/docker/notary"
"github.com/docker/notary/client"
"github.com/docker/notary/client/changelist"
"github.com/docker/notary/passphrase"
"github.com/docker/notary/trustpinning"
"github.com/docker/notary/tuf/data"
"github.com/stretchr/testify/assert"
)
const passwd = "password"
func TestTrustSignCommandErrors(t *testing.T) {
testCases := []struct {
name string
args []string
expectedError string
}{
{
name: "not-enough-args",
expectedError: "requires exactly 1 argument",
},
{
name: "too-many-args",
args: []string{"image", "tag"},
expectedError: "requires exactly 1 argument",
},
{
name: "sha-reference",
args: []string{"870d292919d01a0af7e7f056271dc78792c05f55f49b9b9012b6d89725bd9abd"},
expectedError: "invalid repository name",
},
{
name: "invalid-img-reference",
args: []string{"ALPINE:latest"},
expectedError: "invalid reference format",
},
{
name: "no-tag",
args: []string{"reg/img"},
expectedError: "No tag specified for reg/img",
},
{
name: "digest-reference",
args: []string{"ubuntu@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2"},
expectedError: "cannot use a digest reference for IMAGE:TAG",
},
}
// change to a tmpdir
tmpDir, err := ioutil.TempDir("", "docker-sign-test-")
assert.NoError(t, err)
defer os.RemoveAll(tmpDir)
config.SetDir(tmpDir)
for _, tc := range testCases {
cmd := newSignCommand(
test.NewFakeCli(&fakeClient{}))
cmd.SetArgs(tc.args)
cmd.SetOutput(ioutil.Discard)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}
func TestTrustSignCommandOfflineErrors(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getOfflineNotaryRepository)
cmd := newSignCommand(cli)
cmd.SetArgs([]string{"reg-name.io/image:tag"})
cmd.SetOutput(ioutil.Discard)
assert.Error(t, cmd.Execute())
testutil.ErrorContains(t, cmd.Execute(), "client is offline")
}
func TestGetOrGenerateNotaryKey(t *testing.T) {
tmpDir, err := ioutil.TempDir("", "notary-test-")
assert.NoError(t, err)
defer os.RemoveAll(tmpDir)
notaryRepo, err := client.NewFileCachedRepository(tmpDir, "gun", "https://localhost", nil, passphrase.ConstantRetriever(passwd), trustpinning.TrustPinConfig{})
assert.NoError(t, err)
// repo is empty, try making a root key
rootKeyA, err := getOrGenerateNotaryKey(notaryRepo, data.CanonicalRootRole)
assert.NoError(t, err)
assert.NotNil(t, rootKeyA)
// we should only have one newly generated key
allKeys := notaryRepo.GetCryptoService().ListAllKeys()
assert.Len(t, allKeys, 1)
assert.NotNil(t, notaryRepo.GetCryptoService().GetKey(rootKeyA.ID()))
// this time we should get back the same key if we ask for another root key
rootKeyB, err := getOrGenerateNotaryKey(notaryRepo, data.CanonicalRootRole)
assert.NoError(t, err)
assert.NotNil(t, rootKeyB)
// we should only have one newly generated key
allKeys = notaryRepo.GetCryptoService().ListAllKeys()
assert.Len(t, allKeys, 1)
assert.NotNil(t, notaryRepo.GetCryptoService().GetKey(rootKeyB.ID()))
// The key we retrieved should be identical to the one we generated
assert.Equal(t, rootKeyA, rootKeyB)
// Now also try with a delegation key
releasesKey, err := getOrGenerateNotaryKey(notaryRepo, data.RoleName(trust.ReleasesRole))
assert.NoError(t, err)
assert.NotNil(t, releasesKey)
// we should now have two keys
allKeys = notaryRepo.GetCryptoService().ListAllKeys()
assert.Len(t, allKeys, 2)
assert.NotNil(t, notaryRepo.GetCryptoService().GetKey(releasesKey.ID()))
// The key we retrieved should be identical to the one we generated
assert.NotEqual(t, releasesKey, rootKeyA)
assert.NotEqual(t, releasesKey, rootKeyB)
}
func TestAddStageSigners(t *testing.T) {
tmpDir, err := ioutil.TempDir("", "notary-test-")
assert.NoError(t, err)
defer os.RemoveAll(tmpDir)
notaryRepo, err := client.NewFileCachedRepository(tmpDir, "gun", "https://localhost", nil, passphrase.ConstantRetriever(passwd), trustpinning.TrustPinConfig{})
assert.NoError(t, err)
// stage targets/user
userRole := data.RoleName("targets/user")
userKey := data.NewPublicKey("algoA", []byte("a"))
addStagedSigner(notaryRepo, userRole, []data.PublicKey{userKey})
// check the changelist for four total changes: two on targets/releases and two on targets/user
cl, err := notaryRepo.GetChangelist()
assert.NoError(t, err)
changeList := cl.List()
assert.Len(t, changeList, 4)
// ordering is determinstic:
// first change is for targets/user key creation
newSignerKeyChange := changeList[0]
expectedJSON, err := json.Marshal(&changelist.TUFDelegation{
NewThreshold: notary.MinThreshold,
AddKeys: data.KeyList([]data.PublicKey{userKey}),
})
expectedChange := changelist.NewTUFChange(
changelist.ActionCreate,
userRole,
changelist.TypeTargetsDelegation,
"", // no path for delegations
expectedJSON,
)
assert.Equal(t, expectedChange, newSignerKeyChange)
// second change is for targets/user getting all paths
newSignerPathsChange := changeList[1]
expectedJSON, err = json.Marshal(&changelist.TUFDelegation{
AddPaths: []string{""},
})
expectedChange = changelist.NewTUFChange(
changelist.ActionCreate,
userRole,
changelist.TypeTargetsDelegation,
"", // no path for delegations
expectedJSON,
)
assert.Equal(t, expectedChange, newSignerPathsChange)
releasesRole := data.RoleName("targets/releases")
// third change is for targets/releases key creation
releasesKeyChange := changeList[2]
expectedJSON, err = json.Marshal(&changelist.TUFDelegation{
NewThreshold: notary.MinThreshold,
AddKeys: data.KeyList([]data.PublicKey{userKey}),
})
expectedChange = changelist.NewTUFChange(
changelist.ActionCreate,
releasesRole,
changelist.TypeTargetsDelegation,
"", // no path for delegations
expectedJSON,
)
assert.Equal(t, expectedChange, releasesKeyChange)
// fourth change is for targets/releases getting all paths
releasesPathsChange := changeList[3]
expectedJSON, err = json.Marshal(&changelist.TUFDelegation{
AddPaths: []string{""},
})
expectedChange = changelist.NewTUFChange(
changelist.ActionCreate,
releasesRole,
changelist.TypeTargetsDelegation,
"", // no path for delegations
expectedJSON,
)
assert.Equal(t, expectedChange, releasesPathsChange)
}
func TestGetSignedManifestHashAndSize(t *testing.T) {
tmpDir, err := ioutil.TempDir("", "notary-test-")
assert.NoError(t, err)
defer os.RemoveAll(tmpDir)
notaryRepo, err := client.NewFileCachedRepository(tmpDir, "gun", "https://localhost", nil, passphrase.ConstantRetriever(passwd), trustpinning.TrustPinConfig{})
assert.NoError(t, err)
target := &client.Target{}
target.Hashes, target.Length, err = getSignedManifestHashAndSize(notaryRepo, "test")
assert.EqualError(t, err, "client is offline")
}
func TestGetReleasedTargetHashAndSize(t *testing.T) {
oneReleasedTgt := []client.TargetSignedStruct{}
// make and append 3 non-released signatures on the "unreleased" target
unreleasedTgt := client.Target{Name: "unreleased", Hashes: data.Hashes{notary.SHA256: []byte("hash")}}
for _, unreleasedRole := range []string{"targets/a", "targets/b", "targets/c"} {
oneReleasedTgt = append(oneReleasedTgt, client.TargetSignedStruct{Role: mockDelegationRoleWithName(unreleasedRole), Target: unreleasedTgt})
}
_, _, err := getReleasedTargetHashAndSize(oneReleasedTgt, "unreleased")
assert.EqualError(t, err, "No valid trust data for unreleased")
releasedTgt := client.Target{Name: "released", Hashes: data.Hashes{notary.SHA256: []byte("released-hash")}}
oneReleasedTgt = append(oneReleasedTgt, client.TargetSignedStruct{Role: mockDelegationRoleWithName("targets/releases"), Target: releasedTgt})
hash, _, _ := getReleasedTargetHashAndSize(oneReleasedTgt, "unreleased")
assert.Equal(t, data.Hashes{notary.SHA256: []byte("released-hash")}, hash)
}
func TestCreateTarget(t *testing.T) {
tmpDir, err := ioutil.TempDir("", "notary-test-")
assert.NoError(t, err)
defer os.RemoveAll(tmpDir)
notaryRepo, err := client.NewFileCachedRepository(tmpDir, "gun", "https://localhost", nil, passphrase.ConstantRetriever(passwd), trustpinning.TrustPinConfig{})
assert.NoError(t, err)
_, err = createTarget(notaryRepo, "")
assert.EqualError(t, err, "No tag specified")
_, err = createTarget(notaryRepo, "1")
assert.EqualError(t, err, "client is offline")
}
func TestGetExistingSignatureInfoForReleasedTag(t *testing.T) {
tmpDir, err := ioutil.TempDir("", "notary-test-")
assert.NoError(t, err)
defer os.RemoveAll(tmpDir)
notaryRepo, err := client.NewFileCachedRepository(tmpDir, "gun", "https://localhost", nil, passphrase.ConstantRetriever(passwd), trustpinning.TrustPinConfig{})
assert.NoError(t, err)
_, err = getExistingSignatureInfoForReleasedTag(notaryRepo, "test")
assert.EqualError(t, err, "client is offline")
}
func TestPrettyPrintExistingSignatureInfo(t *testing.T) {
fakeCli := test.NewFakeCli(&fakeClient{})
signers := []string{"Bob", "Alice", "Carol"}
existingSig := trustTagRow{trustTagKey{"tagName", "abc123"}, signers}
prettyPrintExistingSignatureInfo(fakeCli, existingSig)
assert.Contains(t, fakeCli.OutBuffer().String(), "Existing signatures for tag tagName digest abc123 from:\nAlice, Bob, Carol")
}
func TestChangeList(t *testing.T) {
tmpDir, err := ioutil.TempDir("", "docker-sign-test-")
assert.NoError(t, err)
defer os.RemoveAll(tmpDir)
config.SetDir(tmpDir)
cmd := newSignCommand(
test.NewFakeCli(&fakeClient{}))
cmd.SetArgs([]string{"ubuntu:latest"})
cmd.SetOutput(ioutil.Discard)
err = cmd.Execute()
notaryRepo, err := client.NewFileCachedRepository(tmpDir, "docker.io/library/ubuntu", "https://localhost", nil, passphrase.ConstantRetriever(passwd), trustpinning.TrustPinConfig{})
assert.NoError(t, err)
cl, err := notaryRepo.GetChangelist()
assert.Equal(t, len(cl.List()), 0)
}

View File

@ -0,0 +1,6 @@
SIGNED TAG DIGEST SIGNERS
green 677265656e2d646967657374 (Repo Admin)
Administrative keys for signed-repo:
Repository Key: targetsID
Root Key: rootID

View File

@ -0,0 +1,14 @@
SIGNED TAG DIGEST SIGNERS
blue 626c75652d646967657374 alice
green 677265656e2d646967657374 (Repo Admin)
red 7265642d646967657374 alice, bob
List of signers and their keys for signed-repo:
SIGNER KEYS
alice A
bob B
Administrative keys for signed-repo:
Repository Key: targetsID
Root Key: rootID

View File

@ -0,0 +1,6 @@
SIGNED TAG DIGEST SIGNERS
green 677265656e2d646967657374 (Repo Admin)
Administrative keys for signed-repo:
Repository Key: targetsID
Root Key: rootID

View File

@ -0,0 +1,13 @@
No signatures for signed-repo:unsigned
List of signers and their keys for signed-repo:
SIGNER KEYS
alice A
bob B
Administrative keys for signed-repo:
Repository Key: targetsID
Root Key: rootID

229
cli/command/trust/view.go Normal file
View File

@ -0,0 +1,229 @@
package trust
import (
"context"
"encoding/hex"
"fmt"
"io"
"sort"
"strings"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/cli/trust"
"github.com/docker/docker/api/types"
registrytypes "github.com/docker/docker/api/types/registry"
"github.com/docker/notary"
"github.com/docker/notary/client"
"github.com/docker/notary/tuf/data"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
// trustTagKey represents a unique signed tag and hex-encoded hash pair
type trustTagKey struct {
TagName string
HashHex string
}
// trustTagRow encodes all human-consumable information for a signed tag, including signers
type trustTagRow struct {
trustTagKey
Signers []string
}
type trustTagRowList []trustTagRow
func (tagComparator trustTagRowList) Len() int {
return len(tagComparator)
}
func (tagComparator trustTagRowList) Less(i, j int) bool {
return tagComparator[i].TagName < tagComparator[j].TagName
}
func (tagComparator trustTagRowList) Swap(i, j int) {
tagComparator[i], tagComparator[j] = tagComparator[j], tagComparator[i]
}
func newViewCommand(dockerCli command.Cli) *cobra.Command {
cmd := &cobra.Command{
Use: "view [OPTIONS] IMAGE[:TAG]",
Short: "Display detailed information about keys and signatures",
Args: cli.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return lookupTrustInfo(dockerCli, args[0])
},
}
return cmd
}
func lookupTrustInfo(cli command.Cli, remote string) error {
ctx := context.Background()
authResolver := func(ctx context.Context, index *registrytypes.IndexInfo) types.AuthConfig {
return command.ResolveAuthConfig(ctx, cli, index)
}
imgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, authResolver, remote)
if err != nil {
return err
}
tag := imgRefAndAuth.Tag()
notaryRepo, err := cli.NotaryClient(*imgRefAndAuth, trust.ActionsPullOnly)
if err != nil {
return trust.NotaryError(imgRefAndAuth.Reference().Name(), err)
}
if err = clearChangeList(notaryRepo); err != nil {
return err
}
defer clearChangeList(notaryRepo)
// Retrieve all released signatures, match them, and pretty print them
allSignedTargets, err := notaryRepo.GetAllTargetMetadataByName(tag)
if err != nil {
logrus.Debug(trust.NotaryError(imgRefAndAuth.Reference().Name(), err))
// print an empty table if we don't have signed targets, but have an initialized notary repo
if _, ok := err.(client.ErrNoSuchTarget); !ok {
return fmt.Errorf("No signatures or cannot access %s", remote)
}
}
signatureRows := matchReleasedSignatures(allSignedTargets)
if len(signatureRows) > 0 {
if err := printSignatures(cli.Out(), signatureRows); err != nil {
return err
}
} else {
fmt.Fprintf(cli.Out(), "\nNo signatures for %s\n\n", remote)
}
// get the administrative roles
adminRolesWithSigs, err := notaryRepo.ListRoles()
if err != nil {
return fmt.Errorf("No signers for %s", remote)
}
// get delegation roles with the canonical key IDs
delegationRoles, err := notaryRepo.GetDelegationRoles()
if err != nil {
logrus.Debugf("no delegation roles found, or error fetching them for %s: %v", remote, err)
}
signerRoleToKeyIDs := getDelegationRoleToKeyMap(delegationRoles)
// If we do not have additional signers, do not display
if len(signerRoleToKeyIDs) > 0 {
fmt.Fprintf(cli.Out(), "\nList of signers and their keys for %s:\n\n", strings.Split(remote, ":")[0])
printSignerInfo(cli.Out(), signerRoleToKeyIDs)
}
// This will always have the root and targets information
fmt.Fprintf(cli.Out(), "\nAdministrative keys for %s:\n", strings.Split(remote, ":")[0])
printSortedAdminKeys(cli.Out(), adminRolesWithSigs)
return nil
}
func printSortedAdminKeys(out io.Writer, adminRoles []client.RoleWithSignatures) {
sort.Slice(adminRoles, func(i, j int) bool { return adminRoles[i].Name > adminRoles[j].Name })
for _, adminRole := range adminRoles {
fmt.Fprintf(out, "%s", formatAdminRole(adminRole))
}
}
func formatAdminRole(roleWithSigs client.RoleWithSignatures) string {
adminKeyList := roleWithSigs.KeyIDs
sort.Strings(adminKeyList)
var role string
switch roleWithSigs.Name {
case data.CanonicalTargetsRole:
role = "Repository Key"
case data.CanonicalRootRole:
role = "Root Key"
default:
return ""
}
return fmt.Sprintf("%s:\t%s\n", role, strings.Join(adminKeyList, ", "))
}
func getDelegationRoleToKeyMap(rawDelegationRoles []data.Role) map[string][]string {
signerRoleToKeyIDs := make(map[string][]string)
for _, delRole := range rawDelegationRoles {
switch delRole.Name {
case trust.ReleasesRole, data.CanonicalRootRole, data.CanonicalSnapshotRole, data.CanonicalTargetsRole, data.CanonicalTimestampRole:
continue
default:
signerRoleToKeyIDs[notaryRoleToSigner(delRole.Name)] = delRole.KeyIDs
}
}
return signerRoleToKeyIDs
}
// aggregate all signers for a "released" hash+tagname pair. To be "released," the tag must have been
// signed into the "targets" or "targets/releases" role. Output is sorted by tag name
func matchReleasedSignatures(allTargets []client.TargetSignedStruct) trustTagRowList {
signatureRows := trustTagRowList{}
// do a first pass to get filter on tags signed into "targets" or "targets/releases"
releasedTargetRows := map[trustTagKey][]string{}
for _, tgt := range allTargets {
if isReleasedTarget(tgt.Role.Name) {
releasedKey := trustTagKey{tgt.Target.Name, hex.EncodeToString(tgt.Target.Hashes[notary.SHA256])}
releasedTargetRows[releasedKey] = []string{}
}
}
// now fill out all signers on released keys
for _, tgt := range allTargets {
targetKey := trustTagKey{tgt.Target.Name, hex.EncodeToString(tgt.Target.Hashes[notary.SHA256])}
// only considered released targets
if _, ok := releasedTargetRows[targetKey]; ok && !isReleasedTarget(tgt.Role.Name) {
releasedTargetRows[targetKey] = append(releasedTargetRows[targetKey], notaryRoleToSigner(tgt.Role.Name))
}
}
// compile the final output as a sorted slice
for targetKey, signers := range releasedTargetRows {
signatureRows = append(signatureRows, trustTagRow{targetKey, signers})
}
sort.Sort(signatureRows)
return signatureRows
}
// pretty print with ordered rows
func printSignatures(out io.Writer, signatureRows trustTagRowList) error {
trustTagCtx := formatter.Context{
Output: out,
Format: formatter.NewTrustTagFormat(),
}
// convert the formatted type before printing
formattedTags := []formatter.SignedTagInfo{}
for _, sigRow := range signatureRows {
formattedSigners := sigRow.Signers
if len(formattedSigners) == 0 {
formattedSigners = append(formattedSigners, fmt.Sprintf("(%s)", releasedRoleName))
}
formattedTags = append(formattedTags, formatter.SignedTagInfo{
Name: sigRow.TagName,
Digest: sigRow.HashHex,
Signers: formattedSigners,
})
}
return formatter.TrustTagWrite(trustTagCtx, formattedTags)
}
func printSignerInfo(out io.Writer, roleToKeyIDs map[string][]string) error {
signerInfoCtx := formatter.Context{
Output: out,
Format: formatter.NewSignerInfoFormat(),
Trunc: true,
}
formattedSignerInfo := formatter.SignerInfoList{}
for name, keyIDs := range roleToKeyIDs {
formattedSignerInfo = append(formattedSignerInfo, formatter.SignerInfo{
Name: name,
Keys: keyIDs,
})
}
sort.Sort(formattedSignerInfo)
return formatter.SignerInfoWrite(signerInfoCtx, formattedSignerInfo)
}

View File

@ -0,0 +1,433 @@
package trust
import (
"encoding/hex"
"io/ioutil"
"testing"
"github.com/docker/cli/cli/trust"
"github.com/docker/cli/internal/test"
"github.com/docker/cli/internal/test/testutil"
dockerClient "github.com/docker/docker/client"
"github.com/docker/notary"
"github.com/docker/notary/client"
"github.com/docker/notary/tuf/data"
"github.com/gotestyourself/gotestyourself/golden"
"github.com/stretchr/testify/assert"
)
type fakeClient struct {
dockerClient.Client
}
func TestTrustInspectCommandErrors(t *testing.T) {
testCases := []struct {
name string
args []string
expectedError string
}{
{
name: "not-enough-args",
expectedError: "requires exactly 1 argument",
},
{
name: "too-many-args",
args: []string{"remote1", "remote2"},
expectedError: "requires exactly 1 argument",
},
{
name: "sha-reference",
args: []string{"870d292919d01a0af7e7f056271dc78792c05f55f49b9b9012b6d89725bd9abd"},
expectedError: "invalid repository name",
},
{
name: "invalid-img-reference",
args: []string{"ALPINE"},
expectedError: "invalid reference format",
},
}
for _, tc := range testCases {
cmd := newViewCommand(
test.NewFakeCli(&fakeClient{}))
cmd.SetArgs(tc.args)
cmd.SetOutput(ioutil.Discard)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}
func TestTrustInspectCommandOfflineErrors(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getOfflineNotaryRepository)
cmd := newViewCommand(cli)
cmd.SetArgs([]string{"nonexistent-reg-name.io/image"})
cmd.SetOutput(ioutil.Discard)
testutil.ErrorContains(t, cmd.Execute(), "No signatures or cannot access nonexistent-reg-name.io/image")
cli = test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getOfflineNotaryRepository)
cmd = newViewCommand(cli)
cmd.SetArgs([]string{"nonexistent-reg-name.io/image:tag"})
cmd.SetOutput(ioutil.Discard)
testutil.ErrorContains(t, cmd.Execute(), "No signatures or cannot access nonexistent-reg-name.io/image")
}
func TestTrustInspectCommandUninitializedErrors(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getUninitializedNotaryRepository)
cmd := newViewCommand(cli)
cmd.SetArgs([]string{"reg/unsigned-img"})
cmd.SetOutput(ioutil.Discard)
testutil.ErrorContains(t, cmd.Execute(), "No signatures or cannot access reg/unsigned-img")
cli = test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getUninitializedNotaryRepository)
cmd = newViewCommand(cli)
cmd.SetArgs([]string{"reg/unsigned-img:tag"})
cmd.SetOutput(ioutil.Discard)
testutil.ErrorContains(t, cmd.Execute(), "No signatures or cannot access reg/unsigned-img:tag")
}
func TestTrustInspectCommandEmptyNotaryRepoErrors(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getEmptyTargetsNotaryRepository)
cmd := newViewCommand(cli)
cmd.SetArgs([]string{"reg/img:unsigned-tag"})
cmd.SetOutput(ioutil.Discard)
assert.NoError(t, cmd.Execute())
assert.Contains(t, cli.OutBuffer().String(), "No signatures for reg/img:unsigned-tag")
assert.Contains(t, cli.OutBuffer().String(), "Administrative keys for reg/img:")
cli = test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getEmptyTargetsNotaryRepository)
cmd = newViewCommand(cli)
cmd.SetArgs([]string{"reg/img"})
cmd.SetOutput(ioutil.Discard)
assert.NoError(t, cmd.Execute())
assert.Contains(t, cli.OutBuffer().String(), "No signatures for reg/img")
assert.Contains(t, cli.OutBuffer().String(), "Administrative keys for reg/img:")
}
func TestTrustInspectCommandFullRepoWithoutSigners(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getLoadedWithNoSignersNotaryRepository)
cmd := newViewCommand(cli)
cmd.SetArgs([]string{"signed-repo"})
assert.NoError(t, cmd.Execute())
golden.Assert(t, cli.OutBuffer().String(), "trust-inspect-full-repo-no-signers.golden")
}
func TestTrustInspectCommandOneTagWithoutSigners(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getLoadedWithNoSignersNotaryRepository)
cmd := newViewCommand(cli)
cmd.SetArgs([]string{"signed-repo:green"})
assert.NoError(t, cmd.Execute())
golden.Assert(t, cli.OutBuffer().String(), "trust-inspect-one-tag-no-signers.golden")
}
func TestTrustInspectCommandFullRepoWithSigners(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getLoadedNotaryRepository)
cmd := newViewCommand(cli)
cmd.SetArgs([]string{"signed-repo"})
assert.NoError(t, cmd.Execute())
golden.Assert(t, cli.OutBuffer().String(), "trust-inspect-full-repo-with-signers.golden")
}
func TestTrustInspectCommandUnsignedTagInSignedRepo(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getLoadedNotaryRepository)
cmd := newViewCommand(cli)
cmd.SetArgs([]string{"signed-repo:unsigned"})
assert.NoError(t, cmd.Execute())
golden.Assert(t, cli.OutBuffer().String(), "trust-inspect-unsigned-tag-with-signers.golden")
}
func TestNotaryRoleToSigner(t *testing.T) {
assert.Equal(t, releasedRoleName, notaryRoleToSigner(data.CanonicalTargetsRole))
assert.Equal(t, releasedRoleName, notaryRoleToSigner(trust.ReleasesRole))
assert.Equal(t, "signer", notaryRoleToSigner("targets/signer"))
assert.Equal(t, "docker/signer", notaryRoleToSigner("targets/docker/signer"))
// It's nonsense for other base roles to have signed off on a target, but this function leaves role names intact
for _, role := range data.BaseRoles {
if role == data.CanonicalTargetsRole {
continue
}
assert.Equal(t, role.String(), notaryRoleToSigner(role))
}
assert.Equal(t, "notarole", notaryRoleToSigner(data.RoleName("notarole")))
}
// check if a role name is "released": either targets/releases or targets TUF roles
func TestIsReleasedTarget(t *testing.T) {
assert.True(t, isReleasedTarget(trust.ReleasesRole))
for _, role := range data.BaseRoles {
assert.Equal(t, role == data.CanonicalTargetsRole, isReleasedTarget(role))
}
assert.False(t, isReleasedTarget(data.RoleName("targets/not-releases")))
assert.False(t, isReleasedTarget(data.RoleName("random")))
assert.False(t, isReleasedTarget(data.RoleName("targets/releases/subrole")))
}
// creates a mock delegation with a given name and no keys
func mockDelegationRoleWithName(name string) data.DelegationRole {
baseRole := data.NewBaseRole(
data.RoleName(name),
notary.MinThreshold,
)
return data.DelegationRole{baseRole, []string{}}
}
func TestMatchEmptySignatures(t *testing.T) {
// first try empty targets
emptyTgts := []client.TargetSignedStruct{}
matchedSigRows := matchReleasedSignatures(emptyTgts)
assert.Empty(t, matchedSigRows)
}
func TestMatchUnreleasedSignatures(t *testing.T) {
// try an "unreleased" target with 3 signatures, 0 rows will appear
unreleasedTgts := []client.TargetSignedStruct{}
tgt := client.Target{Name: "unreleased", Hashes: data.Hashes{notary.SHA256: []byte("hash")}}
for _, unreleasedRole := range []string{"targets/a", "targets/b", "targets/c"} {
unreleasedTgts = append(unreleasedTgts, client.TargetSignedStruct{Role: mockDelegationRoleWithName(unreleasedRole), Target: tgt})
}
matchedSigRows := matchReleasedSignatures(unreleasedTgts)
assert.Empty(t, matchedSigRows)
}
func TestMatchOneReleasedSingleSignature(t *testing.T) {
// now try only 1 "released" target with no additional sigs, 1 row will appear with 0 signers
oneReleasedTgt := []client.TargetSignedStruct{}
// make and append the "released" target to our mock input
releasedTgt := client.Target{Name: "released", Hashes: data.Hashes{notary.SHA256: []byte("released-hash")}}
oneReleasedTgt = append(oneReleasedTgt, client.TargetSignedStruct{Role: mockDelegationRoleWithName("targets/releases"), Target: releasedTgt})
// make and append 3 non-released signatures on the "unreleased" target
unreleasedTgt := client.Target{Name: "unreleased", Hashes: data.Hashes{notary.SHA256: []byte("hash")}}
for _, unreleasedRole := range []string{"targets/a", "targets/b", "targets/c"} {
oneReleasedTgt = append(oneReleasedTgt, client.TargetSignedStruct{Role: mockDelegationRoleWithName(unreleasedRole), Target: unreleasedTgt})
}
matchedSigRows := matchReleasedSignatures(oneReleasedTgt)
assert.Len(t, matchedSigRows, 1)
outputRow := matchedSigRows[0]
// Empty signers because "targets/releases" doesn't show up
assert.Empty(t, outputRow.Signers)
assert.Equal(t, releasedTgt.Name, outputRow.TagName)
assert.Equal(t, hex.EncodeToString(releasedTgt.Hashes[notary.SHA256]), outputRow.HashHex)
}
func TestMatchOneReleasedMultiSignature(t *testing.T) {
// now try only 1 "released" target with 3 additional sigs, 1 row will appear with 3 signers
oneReleasedTgt := []client.TargetSignedStruct{}
// make and append the "released" target to our mock input
releasedTgt := client.Target{Name: "released", Hashes: data.Hashes{notary.SHA256: []byte("released-hash")}}
oneReleasedTgt = append(oneReleasedTgt, client.TargetSignedStruct{Role: mockDelegationRoleWithName("targets/releases"), Target: releasedTgt})
// make and append 3 non-released signatures on both the "released" and "unreleased" targets
unreleasedTgt := client.Target{Name: "unreleased", Hashes: data.Hashes{notary.SHA256: []byte("hash")}}
for _, unreleasedRole := range []string{"targets/a", "targets/b", "targets/c"} {
oneReleasedTgt = append(oneReleasedTgt, client.TargetSignedStruct{Role: mockDelegationRoleWithName(unreleasedRole), Target: unreleasedTgt})
oneReleasedTgt = append(oneReleasedTgt, client.TargetSignedStruct{Role: mockDelegationRoleWithName(unreleasedRole), Target: releasedTgt})
}
matchedSigRows := matchReleasedSignatures(oneReleasedTgt)
assert.Len(t, matchedSigRows, 1)
outputRow := matchedSigRows[0]
// We should have three signers
assert.Equal(t, outputRow.Signers, []string{"a", "b", "c"})
assert.Equal(t, releasedTgt.Name, outputRow.TagName)
assert.Equal(t, hex.EncodeToString(releasedTgt.Hashes[notary.SHA256]), outputRow.HashHex)
}
func TestMatchMultiReleasedMultiSignature(t *testing.T) {
// now try 3 "released" targets with additional sigs to show 3 rows as follows:
// target-a is signed by targets/releases and targets/a - a will be the signer
// target-b is signed by targets/releases, targets/a, targets/b - a and b will be the signers
// target-c is signed by targets/releases, targets/a, targets/b, targets/c - a, b, and c will be the signers
multiReleasedTgts := []client.TargetSignedStruct{}
// make target-a, target-b, and target-c
targetA := client.Target{Name: "target-a", Hashes: data.Hashes{notary.SHA256: []byte("target-a-hash")}}
targetB := client.Target{Name: "target-b", Hashes: data.Hashes{notary.SHA256: []byte("target-b-hash")}}
targetC := client.Target{Name: "target-c", Hashes: data.Hashes{notary.SHA256: []byte("target-c-hash")}}
// have targets/releases "sign" on all of these targets so they are released
multiReleasedTgts = append(multiReleasedTgts, client.TargetSignedStruct{Role: mockDelegationRoleWithName("targets/releases"), Target: targetA})
multiReleasedTgts = append(multiReleasedTgts, client.TargetSignedStruct{Role: mockDelegationRoleWithName("targets/releases"), Target: targetB})
multiReleasedTgts = append(multiReleasedTgts, client.TargetSignedStruct{Role: mockDelegationRoleWithName("targets/releases"), Target: targetC})
// targets/a signs off on all three targets (target-a, target-b, target-c):
for _, tgt := range []client.Target{targetA, targetB, targetC} {
multiReleasedTgts = append(multiReleasedTgts, client.TargetSignedStruct{Role: mockDelegationRoleWithName("targets/a"), Target: tgt})
}
// targets/b signs off on the final two targets (target-b, target-c):
for _, tgt := range []client.Target{targetB, targetC} {
multiReleasedTgts = append(multiReleasedTgts, client.TargetSignedStruct{Role: mockDelegationRoleWithName("targets/b"), Target: tgt})
}
// targets/c only signs off on the last target (target-c):
multiReleasedTgts = append(multiReleasedTgts, client.TargetSignedStruct{Role: mockDelegationRoleWithName("targets/c"), Target: targetC})
matchedSigRows := matchReleasedSignatures(multiReleasedTgts)
assert.Len(t, matchedSigRows, 3)
// note that the output is sorted by tag name, so we can reliably index to validate data:
outputTargetA := matchedSigRows[0]
assert.Equal(t, outputTargetA.Signers, []string{"a"})
assert.Equal(t, targetA.Name, outputTargetA.TagName)
assert.Equal(t, hex.EncodeToString(targetA.Hashes[notary.SHA256]), outputTargetA.HashHex)
outputTargetB := matchedSigRows[1]
assert.Equal(t, outputTargetB.Signers, []string{"a", "b"})
assert.Equal(t, targetB.Name, outputTargetB.TagName)
assert.Equal(t, hex.EncodeToString(targetB.Hashes[notary.SHA256]), outputTargetB.HashHex)
outputTargetC := matchedSigRows[2]
assert.Equal(t, outputTargetC.Signers, []string{"a", "b", "c"})
assert.Equal(t, targetC.Name, outputTargetC.TagName)
assert.Equal(t, hex.EncodeToString(targetC.Hashes[notary.SHA256]), outputTargetC.HashHex)
}
func TestMatchReleasedSignatureFromTargets(t *testing.T) {
// now try only 1 "released" target with no additional sigs, one rows will appear
oneReleasedTgt := []client.TargetSignedStruct{}
// make and append the "released" target to our mock input
releasedTgt := client.Target{Name: "released", Hashes: data.Hashes{notary.SHA256: []byte("released-hash")}}
oneReleasedTgt = append(oneReleasedTgt, client.TargetSignedStruct{Role: mockDelegationRoleWithName(data.CanonicalTargetsRole.String()), Target: releasedTgt})
matchedSigRows := matchReleasedSignatures(oneReleasedTgt)
assert.Len(t, matchedSigRows, 1)
outputRow := matchedSigRows[0]
// Empty signers because "targets" doesn't show up
assert.Empty(t, outputRow.Signers)
assert.Equal(t, releasedTgt.Name, outputRow.TagName)
assert.Equal(t, hex.EncodeToString(releasedTgt.Hashes[notary.SHA256]), outputRow.HashHex)
}
func TestGetSignerRolesWithKeyIDs(t *testing.T) {
roles := []data.Role{
{
RootRole: data.RootRole{
KeyIDs: []string{"key11"},
},
Name: "targets/alice",
},
{
RootRole: data.RootRole{
KeyIDs: []string{"key21", "key22"},
},
Name: "targets/releases",
},
{
RootRole: data.RootRole{
KeyIDs: []string{"key31"},
},
Name: data.CanonicalTargetsRole,
},
{
RootRole: data.RootRole{
KeyIDs: []string{"key41", "key01"},
},
Name: data.CanonicalRootRole,
},
{
RootRole: data.RootRole{
KeyIDs: []string{"key51"},
},
Name: data.CanonicalSnapshotRole,
},
{
RootRole: data.RootRole{
KeyIDs: []string{"key61"},
},
Name: data.CanonicalTimestampRole,
},
{
RootRole: data.RootRole{
KeyIDs: []string{"key71", "key72"},
},
Name: "targets/bob",
},
}
expectedSignerRoleToKeyIDs := map[string][]string{
"alice": {"key11"},
"bob": {"key71", "key72"},
}
var roleWithSigs []client.RoleWithSignatures
for _, role := range roles {
roleWithSig := client.RoleWithSignatures{Role: role, Signatures: nil}
roleWithSigs = append(roleWithSigs, roleWithSig)
}
signerRoleToKeyIDs := getDelegationRoleToKeyMap(roles)
assert.Equal(t, expectedSignerRoleToKeyIDs, signerRoleToKeyIDs)
}
func TestFormatAdminRole(t *testing.T) {
aliceRole := data.Role{
RootRole: data.RootRole{
KeyIDs: []string{"key11"},
},
Name: "targets/alice",
}
aliceRoleWithSigs := client.RoleWithSignatures{Role: aliceRole, Signatures: nil}
assert.Equal(t, "", formatAdminRole(aliceRoleWithSigs))
releasesRole := data.Role{
RootRole: data.RootRole{
KeyIDs: []string{"key11"},
},
Name: "targets/releases",
}
releasesRoleWithSigs := client.RoleWithSignatures{Role: releasesRole, Signatures: nil}
assert.Equal(t, "", formatAdminRole(releasesRoleWithSigs))
timestampRole := data.Role{
RootRole: data.RootRole{
KeyIDs: []string{"key11"},
},
Name: data.CanonicalTimestampRole,
}
timestampRoleWithSigs := client.RoleWithSignatures{Role: timestampRole, Signatures: nil}
assert.Equal(t, "", formatAdminRole(timestampRoleWithSigs))
snapshotRole := data.Role{
RootRole: data.RootRole{
KeyIDs: []string{"key11"},
},
Name: data.CanonicalSnapshotRole,
}
snapshotRoleWithSigs := client.RoleWithSignatures{Role: snapshotRole, Signatures: nil}
assert.Equal(t, "", formatAdminRole(snapshotRoleWithSigs))
rootRole := data.Role{
RootRole: data.RootRole{
KeyIDs: []string{"key11"},
},
Name: data.CanonicalRootRole,
}
rootRoleWithSigs := client.RoleWithSignatures{Role: rootRole, Signatures: nil}
assert.Equal(t, "Root Key:\tkey11\n", formatAdminRole(rootRoleWithSigs))
targetsRole := data.Role{
RootRole: data.RootRole{
KeyIDs: []string{"key99", "abc", "key11"},
},
Name: data.CanonicalTargetsRole,
}
targetsRoleWithSigs := client.RoleWithSignatures{Role: targetsRole, Signatures: nil}
assert.Equal(t, "Repository Key:\tabc, key11, key99\n", formatAdminRole(targetsRoleWithSigs))
}

View File

@ -1,7 +1,9 @@
package trust package trust
import ( import (
"context"
"encoding/json" "encoding/json"
"io"
"net" "net"
"net/http" "net/http"
"net/url" "net/url"
@ -10,8 +12,8 @@ import (
"path/filepath" "path/filepath"
"time" "time"
"github.com/docker/cli/cli/command"
cliconfig "github.com/docker/cli/cli/config" cliconfig "github.com/docker/cli/cli/config"
"github.com/docker/distribution/reference"
"github.com/docker/distribution/registry/client/auth" "github.com/docker/distribution/registry/client/auth"
"github.com/docker/distribution/registry/client/auth/challenge" "github.com/docker/distribution/registry/client/auth/challenge"
"github.com/docker/distribution/registry/client/transport" "github.com/docker/distribution/registry/client/transport"
@ -27,13 +29,18 @@ import (
"github.com/docker/notary/trustpinning" "github.com/docker/notary/trustpinning"
"github.com/docker/notary/tuf/data" "github.com/docker/notary/tuf/data"
"github.com/docker/notary/tuf/signed" "github.com/docker/notary/tuf/signed"
digest "github.com/opencontainers/go-digest"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
var ( var (
// ReleasesRole is the role named "releases" // ReleasesRole is the role named "releases"
ReleasesRole = path.Join(data.CanonicalTargetsRole, "releases") ReleasesRole = data.RoleName(path.Join(data.CanonicalTargetsRole.String(), "releases"))
// ActionsPullOnly defines the actions for read-only interactions with a Notary Repository
ActionsPullOnly = []string{"pull"}
// ActionsPushAndPull defines the actions for read-write interactions with a Notary Repository
ActionsPushAndPull = []string{"pull", "push"}
) )
func trustDirectory() string { func trustDirectory() string {
@ -86,7 +93,7 @@ func (scs simpleCredentialStore) SetRefreshToken(*url.URL, string, string) {
// GetNotaryRepository returns a NotaryRepository which stores all the // GetNotaryRepository returns a NotaryRepository which stores all the
// information needed to operate on a notary repository. // information needed to operate on a notary repository.
// It creates an HTTP transport providing authentication support. // It creates an HTTP transport providing authentication support.
func GetNotaryRepository(streams command.Streams, repoInfo *registry.RepositoryInfo, authConfig types.AuthConfig, actions ...string) (*client.NotaryRepository, error) { func GetNotaryRepository(in io.Reader, out io.Writer, userAgent string, repoInfo *registry.RepositoryInfo, authConfig *types.AuthConfig, actions ...string) (client.Repository, error) {
server, err := Server(repoInfo.Index) server, err := Server(repoInfo.Index)
if err != nil { if err != nil {
return nil, err return nil, err
@ -119,7 +126,7 @@ func GetNotaryRepository(streams command.Streams, repoInfo *registry.RepositoryI
} }
// Skip configuration headers since request is not going to Docker daemon // Skip configuration headers since request is not going to Docker daemon
modifiers := registry.DockerHeaders(command.UserAgent(), http.Header{}) modifiers := registry.DockerHeaders(userAgent, http.Header{})
authTransport := transport.NewTransport(base, modifiers...) authTransport := transport.NewTransport(base, modifiers...)
pingClient := &http.Client{ pingClient := &http.Client{
Transport: authTransport, Transport: authTransport,
@ -152,7 +159,7 @@ func GetNotaryRepository(streams command.Streams, repoInfo *registry.RepositoryI
Actions: actions, Actions: actions,
Class: repoInfo.Class, Class: repoInfo.Class,
} }
creds := simpleCredentialStore{auth: authConfig} creds := simpleCredentialStore{auth: *authConfig}
tokenHandlerOptions := auth.TokenHandlerOptions{ tokenHandlerOptions := auth.TokenHandlerOptions{
Transport: authTransport, Transport: authTransport,
Credentials: creds, Credentials: creds,
@ -164,23 +171,23 @@ func GetNotaryRepository(streams command.Streams, repoInfo *registry.RepositoryI
modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler)) modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler))
tr := transport.NewTransport(base, modifiers...) tr := transport.NewTransport(base, modifiers...)
return client.NewNotaryRepository( return client.NewFileCachedRepository(
trustDirectory(), trustDirectory(),
repoInfo.Name.Name(), data.GUN(repoInfo.Name.Name()),
server, server,
tr, tr,
getPassphraseRetriever(streams), getPassphraseRetriever(in, out),
trustpinning.TrustPinConfig{}) trustpinning.TrustPinConfig{})
} }
func getPassphraseRetriever(streams command.Streams) notary.PassRetriever { func getPassphraseRetriever(in io.Reader, out io.Writer) notary.PassRetriever {
aliasMap := map[string]string{ aliasMap := map[string]string{
"root": "root", "root": "root",
"snapshot": "repository", "snapshot": "repository",
"targets": "repository", "targets": "repository",
"default": "repository", "default": "repository",
} }
baseRetriever := passphrase.PromptRetrieverWithInOut(streams.In(), streams.Out(), aliasMap) baseRetriever := passphrase.PromptRetrieverWithInOut(in, out, aliasMap)
env := map[string]string{ env := map[string]string{
"root": os.Getenv("DOCKER_CONTENT_TRUST_ROOT_PASSPHRASE"), "root": os.Getenv("DOCKER_CONTENT_TRUST_ROOT_PASSPHRASE"),
"snapshot": os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"), "snapshot": os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
@ -193,7 +200,7 @@ func getPassphraseRetriever(streams command.Streams) notary.PassRetriever {
return v, numAttempts > 1, nil return v, numAttempts > 1, nil
} }
// For non-root roles, we can also try the "default" alias if it is specified // For non-root roles, we can also try the "default" alias if it is specified
if v := env["default"]; v != "" && alias != data.CanonicalRootRole { if v := env["default"]; v != "" && alias != data.CanonicalRootRole.String() {
return v, numAttempts > 1, nil return v, numAttempts > 1, nil
} }
return baseRetriever(keyName, alias, createNew, numAttempts) return baseRetriever(keyName, alias, createNew, numAttempts)
@ -230,3 +237,130 @@ func NotaryError(repoName string, err error) error {
return err return err
} }
// GetSignableRoles returns a list of roles for which we have valid signing
// keys, given a notary repository and a target
func GetSignableRoles(repo client.Repository, target *client.Target) ([]data.RoleName, error) {
var signableRoles []data.RoleName
// translate the full key names, which includes the GUN, into just the key IDs
allCanonicalKeyIDs := make(map[string]struct{})
for fullKeyID := range repo.GetCryptoService().ListAllKeys() {
allCanonicalKeyIDs[path.Base(fullKeyID)] = struct{}{}
}
allDelegationRoles, err := repo.GetDelegationRoles()
if err != nil {
return signableRoles, err
}
// if there are no delegation roles, then just try to sign it into the targets role
if len(allDelegationRoles) == 0 {
signableRoles = append(signableRoles, data.CanonicalTargetsRole)
return signableRoles, nil
}
// there are delegation roles, find every delegation role we have a key for, and
// attempt to sign into into all those roles.
for _, delegationRole := range allDelegationRoles {
// We do not support signing any delegation role that isn't a direct child of the targets role.
// Also don't bother checking the keys if we can't add the target
// to this role due to path restrictions
if path.Dir(delegationRole.Name.String()) != data.CanonicalTargetsRole.String() || !delegationRole.CheckPaths(target.Name) {
continue
}
for _, canonicalKeyID := range delegationRole.KeyIDs {
if _, ok := allCanonicalKeyIDs[canonicalKeyID]; ok {
signableRoles = append(signableRoles, delegationRole.Name)
break
}
}
}
if len(signableRoles) == 0 {
return signableRoles, errors.Errorf("no valid signing keys for delegation roles")
}
return signableRoles, nil
}
// ImageRefAndAuth contains all reference information and the auth config for an image request
type ImageRefAndAuth struct {
authConfig *types.AuthConfig
reference reference.Named
repoInfo *registry.RepositoryInfo
tag string
digest digest.Digest
}
// NewImageRefAndAuth creates a new ImageRefAndAuth struct
func NewImageRefAndAuth(authConfig *types.AuthConfig, reference reference.Named, repoInfo *registry.RepositoryInfo, tag string, digest digest.Digest) *ImageRefAndAuth {
return &ImageRefAndAuth{authConfig, reference, repoInfo, tag, digest}
}
// GetImageReferencesAndAuth retrieves the necessary reference and auth information for an image name
// as a ImageRefAndAuth struct
func GetImageReferencesAndAuth(ctx context.Context, authResolver func(ctx context.Context, index *registrytypes.IndexInfo) types.AuthConfig, imgName string) (*ImageRefAndAuth, error) {
ref, err := reference.ParseNormalizedNamed(imgName)
if err != nil {
return nil, err
}
// Resolve the Repository name from fqn to RepositoryInfo
repoInfo, err := registry.ParseRepositoryInfo(ref)
if err != nil {
return nil, err
}
authConfig := authResolver(ctx, repoInfo.Index)
return NewImageRefAndAuth(&authConfig, ref, repoInfo, getTag(ref), getDigest(ref)), nil
}
func getTag(ref reference.Named) string {
switch x := ref.(type) {
case reference.Canonical, reference.Digested:
return ""
case reference.NamedTagged:
return x.Tag()
default:
return ""
}
}
func getDigest(ref reference.Named) digest.Digest {
switch x := ref.(type) {
case reference.Canonical:
return x.Digest()
case reference.Digested:
return x.Digest()
default:
return digest.Digest("")
}
}
// AuthConfig returns the auth information (username, etc) for a given ImageRefAndAuth
func (imgRefAuth *ImageRefAndAuth) AuthConfig() *types.AuthConfig {
return imgRefAuth.authConfig
}
// Reference returns the Image reference for a given ImageRefAndAuth
func (imgRefAuth *ImageRefAndAuth) Reference() reference.Named {
return imgRefAuth.reference
}
// RepoInfo returns the repository information for a given ImageRefAndAuth
func (imgRefAuth *ImageRefAndAuth) RepoInfo() *registry.RepositoryInfo {
return imgRefAuth.repoInfo
}
// Tag returns the Image tag for a given ImageRefAndAuth
func (imgRefAuth *ImageRefAndAuth) Tag() string {
return imgRefAuth.tag
}
// Digest returns the Image digest for a given ImageRefAndAuth
func (imgRefAuth *ImageRefAndAuth) Digest() digest.Digest {
return imgRefAuth.digest
}

59
cli/trust/trust_test.go Normal file
View File

@ -0,0 +1,59 @@
package trust
import (
"io/ioutil"
"os"
"testing"
"github.com/docker/distribution/reference"
"github.com/docker/notary/client"
"github.com/docker/notary/passphrase"
"github.com/docker/notary/trustpinning"
digest "github.com/opencontainers/go-digest"
"github.com/stretchr/testify/assert"
)
func TestGetTag(t *testing.T) {
ref, err := reference.ParseNormalizedNamed("ubuntu@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2")
assert.NoError(t, err)
tag := getTag(ref)
assert.Equal(t, "", tag)
ref, err = reference.ParseNormalizedNamed("alpine:latest")
assert.NoError(t, err)
tag = getTag(ref)
assert.Equal(t, tag, "latest")
ref, err = reference.ParseNormalizedNamed("alpine")
assert.NoError(t, err)
tag = getTag(ref)
assert.Equal(t, tag, "")
}
func TestGetDigest(t *testing.T) {
ref, err := reference.ParseNormalizedNamed("ubuntu@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2")
assert.NoError(t, err)
d := getDigest(ref)
assert.Equal(t, digest.Digest("sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2"), d)
ref, err = reference.ParseNormalizedNamed("alpine:latest")
assert.NoError(t, err)
d = getDigest(ref)
assert.Equal(t, digest.Digest(""), d)
ref, err = reference.ParseNormalizedNamed("alpine")
assert.NoError(t, err)
d = getDigest(ref)
assert.Equal(t, digest.Digest(""), d)
}
func TestGetSignableRolesError(t *testing.T) {
tmpDir, err := ioutil.TempDir("", "notary-test-")
assert.NoError(t, err)
defer os.RemoveAll(tmpDir)
notaryRepo, err := client.NewFileCachedRepository(tmpDir, "gun", "https://localhost", nil, passphrase.ConstantRetriever("password"), trustpinning.TrustPinConfig{})
target := client.Target{}
_, err = GetSignableRoles(notaryRepo, &target)
assert.EqualError(t, err, "client is offline")
}

View File

@ -0,0 +1,132 @@
---
title: "trust revoke"
description: "The revoke command description and usage"
keywords: "revoke, notary, trust"
---
<!-- This file is maintained within the docker/cli Github
repository at https://github.com/docker/cli/. Make all
pull requests against that repo. If you see this file in
another repository, consider it read-only there, as it will
periodically be overwritten by the definitive file. Pull
requests which include edits to this file in other repositories
will be rejected.
-->
# trust revoke
```markdown
Usage: docker trust revoke [OPTIONS] IMAGE[:TAG]
Remove trust for an image
Options:
--help Print usage
-y, --yes Do not prompt for confirmation
```
## Description
`docker trust revoke` removes signatures from tags in signed repositories.
`docker trust revoke` is currently experimental.
## Examples
### Revoke signatures from a signed tag
Here's an example of a repo with two signed tags:
```bash
$ docker trust view example/trust-demo
SIGNED TAG DIGEST SIGNERS
red 852cc04935f930a857b630edc4ed6131e91b22073bcc216698842e44f64d2943 alice
blue f1c38dbaeeb473c36716f6494d803fbfbe9d8a76916f7c0093f227821e378197 alice, bob
List of signers and their keys for example/trust-demo:
SIGNER KEYS
alice 05e87edcaecb
bob 5600f5ab76a2
Administrative keys for example/trust-demo:
Repository Key: ecc457614c9fc399da523a5f4e24fe306a0a6ee1cc79a10e4555b3c6ab02f71e
Root Key: 3cb2228f6561e58f46dbc4cda4fcaff9d5ef22e865a94636f82450d1d2234949
```
When `alice`, one of the signers, runs `docker trust revoke`:
```bash
$ docker trust revoke example/trust-demo:red
Enter passphrase for delegation key with ID 27d42a8:
Successfully deleted signature for example/trust-demo:red
```
After revocation, the tag is removed from the list of released tags:
```bash
$ docker trust view example/trust-demo
SIGNED TAG DIGEST SIGNERS
blue f1c38dbaeeb473c36716f6494d803fbfbe9d8a76916f7c0093f227821e378197 alice, bob
List of signers and their keys for example/trust-demo:
SIGNER KEYS
alice 05e87edcaecb
bob 5600f5ab76a2
Administrative keys for example/trust-demo:
Repository Key: ecc457614c9fc399da523a5f4e24fe306a0a6ee1cc79a10e4555b3c6ab02f71e
Root Key: 3cb2228f6561e58f46dbc4cda4fcaff9d5ef22e865a94636f82450d1d2234949
```
### Revoke signatures on all tags in a repository
When no tag is specified, `docker trust` revokes all signatures that you have a signing key for.
```bash
$ docker trust view example/trust-demo
SIGNED TAG DIGEST SIGNERS
red 852cc04935f930a857b630edc4ed6131e91b22073bcc216698842e44f64d2943 alice
blue f1c38dbaeeb473c36716f6494d803fbfbe9d8a76916f7c0093f227821e378197 alice, bob
List of signers and their keys for example/trust-demo:
SIGNER KEYS
alice 05e87edcaecb
bob 5600f5ab76a2
Administrative keys for example/trust-demo:
Repository Key: ecc457614c9fc399da523a5f4e24fe306a0a6ee1cc79a10e4555b3c6ab02f71e
Root Key: 3cb2228f6561e58f46dbc4cda4fcaff9d5ef22e865a94636f82450d1d2234949
```
When `alice`, one of the signers, runs `docker trust revoke`:
```bash
$ docker trust revoke example/trust-demo
Please confirm you would like to delete all signature data for example/trust-demo? [y/N] y
Enter passphrase for delegation key with ID 27d42a8:
Successfully deleted signature for example/trust-demo
```
All tags that have `alice`'s signature on them are removed from the list of released tags:
```bash
$ docker trust view example/trust-demo
No signatures for example/trust-demo
List of signers and their keys for example/trust-demo:
SIGNER KEYS
alice 05e87edcaecb
bob 5600f5ab76a2
Administrative keys for example/trust-demo:
Repository Key: ecc457614c9fc399da523a5f4e24fe306a0a6ee1cc79a10e4555b3c6ab02f71e
Root Key: 3cb2228f6561e58f46dbc4cda4fcaff9d5ef22e865a94636f82450d1d2234949
```

View File

@ -0,0 +1,182 @@
---
title: "trust sign"
description: "The sign command description and usage"
keywords: "sign, notary, trust"
---
<!-- This file is maintained within the docker/cli Github
repository at https://github.com/docker/cli/. Make all
pull requests against that repo. If you see this file in
another repository, consider it read-only there, as it will
periodically be overwritten by the definitive file. Pull
requests which include edits to this file in other repositories
will be rejected.
-->
# trust sign
```markdown
Usage: docker trust sign [OPTIONS] IMAGE:TAG
Sign an image
```
## Description
`docker trust sign` adds signatures to tags to create signed repositories.
`docker trust sign` is currently experimental.
## Examples
### Sign a tag as a repo admin
Given an image:
```bash
$ docker trust view example/trust-demo
SIGNED TAG DIGEST SIGNERS
v1 c24134c079c35e698060beabe110bb83ab285d0d978de7d92fed2c8c83570a41 (Repo Admin)
Administrative keys for example/trust-demo:
Repository Key: 36d4c3601102fa7c5712a343c03b94469e5835fb27c191b529c06fd19c14a942
Root Key: 246d360f7c53a9021ee7d4259e3c5692f3f1f7ad4737b1ea8c7b8da741ad980b
```
Sign a new tag with `docker trust sign`:
```bash
$ docker trust sign example/trust-demo:v2
Signing and pushing trust metadata for example/trust-demo:v2
The push refers to a repository [docker.io/example/trust-demo]
eed4e566104a: Layer already exists
77edfb6d1e3c: Layer already exists
c69f806905c2: Layer already exists
582f327616f1: Layer already exists
a3fbb648f0bd: Layer already exists
5eac2de68a97: Layer already exists
8d4d1ab5ff74: Layer already exists
v2: digest: sha256:8f6f460abf0436922df7eb06d28b3cdf733d2cac1a185456c26debbff0839c56 size: 1787
Signing and pushing trust metadata
Enter passphrase for repository key with ID 36d4c36:
Successfully signed "docker.io/example/trust-demo":v2
```
`docker trust view` lists the new signature:
```bash
$ docker trust view example/trust-demo
SIGNED TAG DIGEST SIGNERS
v1 c24134c079c35e698060beabe110bb83ab285d0d978de7d92fed2c8c83570a41 (Repo Admin)
v2 8f6f460abf0436922df7eb06d28b3cdf733d2cac1a185456c26debbff0839c56 (Repo Admin)
Administrative keys for example/trust-demo:
Repository Key: 36d4c3601102fa7c5712a343c03b94469e5835fb27c191b529c06fd19c14a942
Root Key: 246d360f7c53a9021ee7d4259e3c5692f3f1f7ad4737b1ea8c7b8da741ad980b
```
### Sign a tag as a signer
Given an image:
```bash
$ docker trust view example/trust-demo
No signatures for example/trust-demo
List of signers and their keys for example/trust-demo:
SIGNER KEYS
alice 05e87edcaecb
bob 5600f5ab76a2
Administrative keys for example/trust-demo:
Repository Key: ecc457614c9fc399da523a5f4e24fe306a0a6ee1cc79a10e4555b3c6ab02f71e
Root Key: 3cb2228f6561e58f46dbc4cda4fcaff9d5ef22e865a94636f82450d1d2234949
```
Sign a new tag with `docker trust sign`:
```bash
$ docker trust sign example/trust-demo:v1
Signing and pushing trust metadata for example/trust-demo:v1
The push refers to a repository [docker.io/example/trust-demo]
26b126eb8632: Layer already exists
220d34b5f6c9: Layer already exists
8a5132998025: Layer already exists
aca233ed29c3: Layer already exists
e5d2f035d7a4: Layer already exists
v1: digest: sha256:74d4bfa917d55d53c7df3d2ab20a8d926874d61c3da5ef6de15dd2654fc467c4 size: 1357
Signing and pushing trust metadata
Enter passphrase for delegation key with ID 27d42a8:
Successfully signed "docker.io/example/trust-demo":v1
```
`docker trust view` lists the new signature:
```bash
$ docker trust view example/trust-demo
SIGNED TAG DIGEST SIGNERS
v1 74d4bfa917d55d53c7df3d2ab20a8d926874d61c3da5ef6de15dd2654fc467c4 alice
List of signers and their keys for example/trust-demo:
SIGNER KEYS
alice 05e87edcaecb
bob 5600f5ab76a2
Administrative keys for example/trust-demo:
Repository Key: ecc457614c9fc399da523a5f4e24fe306a0a6ee1cc79a10e4555b3c6ab02f71e
Root Key: 3cb2228f6561e58f46dbc4cda4fcaff9d5ef22e865a94636f82450d1d2234949
```
## Initialize a new repo and sign a tag
When signing an image on a repo for the first time, `docker trust sign` sets up new keys before signing the image.
```bash
$ docker trust view example/trust-demo
No signatures or cannot access example/trust-demo
```
```bash
$ docker trust sign example/trust-demo:v1
Signing and pushing trust metadata for example/trust-demo:v1
Enter passphrase for root key with ID 36cac18:
Enter passphrase for new repository key with ID 731396b:
Repeat passphrase for new repository key with ID 731396b:
Enter passphrase for new alice key with ID 6d52b29:
Repeat passphrase for new alice key with ID 6d52b29:
Created signer: alice
Finished initializing "docker.io/example/trust-demo"
The push refers to a repository [docker.io/example/trust-demo]
eed4e566104a: Layer already exists
77edfb6d1e3c: Layer already exists
c69f806905c2: Layer already exists
582f327616f1: Layer already exists
a3fbb648f0bd: Layer already exists
5eac2de68a97: Layer already exists
8d4d1ab5ff74: Layer already exists
v1: digest: sha256:8f6f460abf0436922df7eb06d28b3cdf733d2cac1a185456c26debbff0839c56 size: 1787
Signing and pushing trust metadata
Enter passphrase for alice key with ID 6d52b29:
Successfully signed "docker.io/example/trust-demo":v1
```
```bash
$ docker trust view example/trust-demo
SIGNED TAG DIGEST SIGNERS
v1 8f6f460abf0436922df7eb06d28b3cdf733d2cac1a185456c26debbff0839c56 alice
List of signers and their keys for example/trust-demo:
SIGNER KEYS
alice 6d52b29d940f
Administrative keys for example/trust-demo:
Repository Key: 731396b65eac3ef5ec01406801bdfb70feb40c17808d2222427c18046eb63beb
Root Key: 70d174714bd1461f6c58cb3ef39087c8fdc7633bb11a98af844fd9a04e208103
```

View File

@ -0,0 +1,138 @@
---
title: "trust view"
description: "The view command description and usage"
keywords: "view, notary, trust"
---
<!-- This file is maintained within the docker/cli Github
repository at https://github.com/docker/cli/. Make all
pull requests against that repo. If you see this file in
another repository, consider it read-only there, as it will
periodically be overwritten by the definitive file. Pull
requests which include edits to this file in other repositories
will be rejected.
-->
# trust view
```markdown
Usage: docker trust view [OPTIONS] IMAGE[:TAG]
Display detailed information about keys and signatures
```
## Description
`docker trust view` provides detailed information on signed repositories.
This includes all image tags that are signed, who signed them, and who can sign
new tags.
By default, `docker trust view` renders results in a table.
`docker trust view` is currently experimental.
## Examples
### Get details about signatures for a single image tag
```bash
$ docker trust view alpine:latest
SIGNED TAG DIGEST SIGNERS
latest 1072e499f3f655a032e88542330cf75b02e7bdf673278f701d7ba61629ee3ebe (Repo Admin)
Administrative keys for alpine:latest:
Repository Key: 5a46c9aaa82ff150bb7305a2d17d0c521c2d784246807b2dc611f436a69041fd
Root Key: a2489bcac7a79aa67b19b96c4a3bf0c675ffdf00c6d2fabe1a5df1115e80adce
```
The `SIGNED TAG` is the signed image tag with a unique content-addressable `DIGEST`. `SIGNERS` lists all entities who have signed.
The administrative keys listed specify the root key of trust, as well as the administrative repository key. These keys are responsible for modifying signers, and rotating keys for the signed repository.
If signers are set up for the repository via other `docker trust` commands, `docker trust view` displays them appropriately as a `SIGNER` and specify their `KEYS`:
```bash
$ docker trust view my-image:purple
SIGNED TAG DIGEST SIGNERS
purple 941d3dba358621ce3c41ef67b47cf80f701ff80cdf46b5cc86587eaebfe45557 alice, bob, carol
List of signers and their keys:
SIGNER KEYS
alice 47caae5b3e61, a85aab9d20a4
bob 034370bcbd77, 82a66673242c
carol b6f9f8e1aab0
Administrative keys for my-image:
Repository Key: 27df2c8187e7543345c2e0bf3a1262e0bc63a72754e9a7395eac3f747ec23a44
Root Key: 40b66ccc8b176be8c7d365a17f3e046d1c3494e053dd57cfeacfe2e19c4f8e8f
```
If the image tag is unsigned or unavailable, `docker trust view` does not display any signed tags.
```bash
$ docker trust view unsigned-img
No signatures or cannot access unsigned-img
```
However, if other tags are signed in the same image repository, `docker trust view` reports relevant key information.
```bash
$ docker trust view alpine:unsigned
No signatures for alpine:unsigned
Administrative keys for alpine:unsigned:
Repository Key: 5a46c9aaa82ff150bb7305a2d17d0c521c2d784246807b2dc611f436a69041fd
Root Key: a2489bcac7a79aa67b19b96c4a3bf0c675ffdf00c6d2fabe1a5df1115e80adce
```
### Get details about signatures for all image tags in a repository
```bash
$ docker trust view alpine
SIGNED TAG DIGEST SIGNERS
2.6 9ace551613070689a12857d62c30ef0daa9a376107ec0fff0e34786cedb3399b (Repo Admin)
2.7 9f08005dff552038f0ad2f46b8e65ff3d25641747d3912e3ea8da6785046561a (Repo Admin)
3.1 d9477888b78e8c6392e0be8b2e73f8c67e2894ff9d4b8e467d1488fcceec21c8 (Repo Admin)
3.2 19826d59171c2eb7e90ce52bfd822993bef6a6fe3ae6bb4a49f8c1d0a01e99c7 (Repo Admin)
3.3 8fd4b76819e1e5baac82bd0a3d03abfe3906e034cc5ee32100d12aaaf3956dc7 (Repo Admin)
3.4 833ad81ace8277324f3ca8c91c02bdcf1d13988d8ecf8a3f97ecdd69d0390ce9 (Repo Admin)
3.5 af2a5bd2f8de8fc1ecabf1c76611cdc6a5f1ada1a2bdd7d3816e121b70300308 (Repo Admin)
3.6 1072e499f3f655a032e88542330cf75b02e7bdf673278f701d7ba61629ee3ebe (Repo Admin)
edge 79d50d15bd7ea48ea00cf3dd343b0e740c1afaa8e899bee475236ef338e1b53b (Repo Admin)
latest 1072e499f3f655a032e88542330cf75b02e7bdf673278f701d7ba61629ee3ebe (Repo Admin)
Administrative keys for alpine:
Repository Key: 5a46c9aaa82ff150bb7305a2d17d0c521c2d784246807b2dc611f436a69041fd
Root Key: a2489bcac7a79aa67b19b96c4a3bf0c675ffdf00c6d2fabe1a5df1115e80adce
```
Here's an example with signers that are set up by `docker trust` commands:
```bash
$ docker trust view my-image
SIGNED TAG DIGEST SIGNERS
red 852cc04935f930a857b630edc4ed6131e91b22073bcc216698842e44f64d2943 alice
blue f1c38dbaeeb473c36716f6494d803fbfbe9d8a76916f7c0093f227821e378197 alice, bob
green cae8fedc840f90c8057e1c24637d11865743ab1e61a972c1c9da06ec2de9a139 alice, bob
yellow 9cc65fc3126790e683d1b92f307a71f48f75fa7dd47a7b03145a123eaf0b45ba carol
purple 941d3dba358621ce3c41ef67b47cf80f701ff80cdf46b5cc86587eaebfe45557 alice, bob, carol
orange d6c271baa6d271bcc24ef1cbd65abf39123c17d2e83455bdab545a1a9093fc1c alice
List of signers and their keys for my-image:
SIGNER KEYS
alice 47caae5b3e61, a85aab9d20a4
bob 034370bcbd77, 82a66673242c
carol b6f9f8e1aab0
Administrative keys for my-image:
Repository Key: 27df2c8187e7543345c2e0bf3a1262e0bc63a72754e9a7395eac3f747ec23a44
Root Key: 40b66ccc8b176be8c7d365a17f3e046d1c3494e053dd57cfeacfe2e19c4f8e8f
```

View File

@ -2,25 +2,31 @@ package test
import ( import (
"bytes" "bytes"
"fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"strings" "strings"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/config/configfile" "github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/cli/trust"
"github.com/docker/docker/client" "github.com/docker/docker/client"
notaryclient "github.com/docker/notary/client"
) )
type notaryClientFuncType func(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (notaryclient.Repository, error)
// FakeCli emulates the default DockerCli // FakeCli emulates the default DockerCli
type FakeCli struct { type FakeCli struct {
command.DockerCli command.DockerCli
client client.APIClient client client.APIClient
configfile *configfile.ConfigFile configfile *configfile.ConfigFile
out *command.OutStream out *command.OutStream
outBuffer *bytes.Buffer outBuffer *bytes.Buffer
err *bytes.Buffer err *bytes.Buffer
in *command.InStream in *command.InStream
server command.ServerInfo server command.ServerInfo
notaryClientFunc notaryClientFuncType
} }
// NewFakeCli returns a fake for the command.Cli interface // NewFakeCli returns a fake for the command.Cli interface
@ -91,3 +97,16 @@ func (c *FakeCli) OutBuffer() *bytes.Buffer {
func (c *FakeCli) ErrBuffer() *bytes.Buffer { func (c *FakeCli) ErrBuffer() *bytes.Buffer {
return c.err return c.err
} }
// SetNotaryClient sets the internal getter for retrieving a NotaryClient
func (c *FakeCli) SetNotaryClient(notaryClientFunc notaryClientFuncType) {
c.notaryClientFunc = notaryClientFunc
}
// NotaryClient returns an err for testing unless defined
func (c *FakeCli) NotaryClient(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (notaryclient.Repository, error) {
if c.notaryClientFunc != nil {
return c.notaryClientFunc(imgRefAndAuth, actions)
}
return nil, fmt.Errorf("no notary client available unless defined")
}

View File

@ -13,8 +13,8 @@ github.com/docker/go d30aec9fd63c35133f8f79c3412ad91a3b08be06
github.com/docker/go-connections 3ede32e2033de7505e6500d6c868c2b9ed9f169d github.com/docker/go-connections 3ede32e2033de7505e6500d6c868c2b9ed9f169d
github.com/docker/go-events 9461782956ad83b30282bf90e31fa6a70c255ba9 github.com/docker/go-events 9461782956ad83b30282bf90e31fa6a70c255ba9
github.com/docker/go-units 9e638d38cf6977a37a8ea0078f3ee75a7cdb2dd1 github.com/docker/go-units 9e638d38cf6977a37a8ea0078f3ee75a7cdb2dd1
github.com/docker/notary v0.4.2-sirupsen https://github.com/simonferquel/notary.git github.com/docker/notary 8a1de3cfc3f1408e54d6364fc949214a4883a9f3
github.com/docker/swarmkit 0554c9bc9a485025e89b8e5c2c1f0d75961906a2 github.com/docker/swarmkit 79381d0840be27f8b3f5c667b348a4467d866eeb
github.com/flynn-archive/go-shlex 3f9db97f856818214da2e1057f8ad84803971cff github.com/flynn-archive/go-shlex 3f9db97f856818214da2e1057f8ad84803971cff
github.com/gogo/protobuf v0.4 github.com/gogo/protobuf v0.4
github.com/golang/protobuf 7a211bcf3bce0e3f1d74f9894916e6f116ae83b4 github.com/golang/protobuf 7a211bcf3bce0e3f1d74f9894916e6f116ae83b4

View File

@ -1,4 +1,4 @@
Apache License Apache License
Version 2.0, January 2004 Version 2.0, January 2004
http://www.apache.org/licenses/ http://www.apache.org/licenses/

View File

@ -78,18 +78,21 @@ to use `notary` with Docker images.
## Building Notary ## Building Notary
Note that our [latest stable release](https://github.com/docker/notary/releases) is at the head of the
[releases branch](https://github.com/docker/notary/tree/releases). The master branch is the development
branch and contains features for the next release.
Prerequisites: Prerequisites:
- Go >= 1.7 - Go >= 1.7.1
- [godep](https://github.com/tools/godep) installed - [godep](https://github.com/tools/godep) installed
- libtool development headers installed - libtool development headers installed
- Ubuntu: `apt-get install libltdl-dev` - Ubuntu: `apt-get install libltdl-dev`
- CentOS/RedHat: `yum install libtool-ltdl-devel` - CentOS/RedHat: `yum install libtool-ltdl-devel`
- Mac OS ([Homebrew](http://brew.sh/)): `brew install libtool` - Mac OS ([Homebrew](http://brew.sh/)): `brew install libtool`
Run `make binaries`, which creates the Notary Client CLI binary at `bin/notary`. Run `make client`, which creates the Notary Client CLI binary at `bin/notary`.
Note that `make binaries` assumes a standard Go directory structure, in which Note that `make client` assumes a standard Go directory structure, in which
Notary is checked out to the `src` directory in your `GOPATH`. For example: Notary is checked out to the `src` directory in your `GOPATH`. For example:
``` ```
$GOPATH/ $GOPATH/
@ -98,3 +101,5 @@ $GOPATH/
docker/ docker/
notary/ notary/
``` ```
To build the server and signer, please run `docker-compose build`.

View File

@ -8,10 +8,8 @@ import (
// Unfortunately because of targets delegations, we can only // Unfortunately because of targets delegations, we can only
// cover the base roles. // cover the base roles.
const ( const (
ScopeRoot = "root" ScopeRoot = "root"
ScopeTargets = "targets" ScopeTargets = "targets"
ScopeSnapshot = "snapshot"
ScopeTimestamp = "timestamp"
) )
// Types for TUFChanges are namespaced by the Role they // Types for TUFChanges are namespaced by the Role they
@ -20,7 +18,7 @@ const (
// all changes in Snapshot and Timestamp are programmatically // all changes in Snapshot and Timestamp are programmatically
// generated base on Root and Targets changes. // generated base on Root and Targets changes.
const ( const (
TypeRootRole = "role" TypeBaseRole = "role"
TypeTargetsTarget = "target" TypeTargetsTarget = "target"
TypeTargetsDelegation = "delegation" TypeTargetsDelegation = "delegation"
TypeWitness = "witness" TypeWitness = "witness"
@ -29,22 +27,22 @@ const (
// TUFChange represents a change to a TUF repo // TUFChange represents a change to a TUF repo
type TUFChange struct { type TUFChange struct {
// Abbreviated because Go doesn't permit a field and method of the same name // Abbreviated because Go doesn't permit a field and method of the same name
Actn string `json:"action"` Actn string `json:"action"`
Role string `json:"role"` Role data.RoleName `json:"role"`
ChangeType string `json:"type"` ChangeType string `json:"type"`
ChangePath string `json:"path"` ChangePath string `json:"path"`
Data []byte `json:"data"` Data []byte `json:"data"`
} }
// TUFRootData represents a modification of the keys associated // TUFRootData represents a modification of the keys associated
// with a role that appears in the root.json // with a role that appears in the root.json
type TUFRootData struct { type TUFRootData struct {
Keys data.KeyList `json:"keys"` Keys data.KeyList `json:"keys"`
RoleName string `json:"role"` RoleName data.RoleName `json:"role"`
} }
// NewTUFChange initializes a TUFChange object // NewTUFChange initializes a TUFChange object
func NewTUFChange(action string, role, changeType, changePath string, content []byte) *TUFChange { func NewTUFChange(action string, role data.RoleName, changeType, changePath string, content []byte) *TUFChange {
return &TUFChange{ return &TUFChange{
Actn: action, Actn: action,
Role: role, Role: role,
@ -60,7 +58,7 @@ func (c TUFChange) Action() string {
} }
// Scope returns c.Role // Scope returns c.Role
func (c TUFChange) Scope() string { func (c TUFChange) Scope() data.RoleName {
return c.Role return c.Role
} }
@ -83,17 +81,17 @@ func (c TUFChange) Content() []byte {
// this includes creating a delegations. This format is used to avoid // this includes creating a delegations. This format is used to avoid
// unexpected race conditions between humans modifying the same delegation // unexpected race conditions between humans modifying the same delegation
type TUFDelegation struct { type TUFDelegation struct {
NewName string `json:"new_name,omitempty"` NewName data.RoleName `json:"new_name,omitempty"`
NewThreshold int `json:"threshold, omitempty"` NewThreshold int `json:"threshold, omitempty"`
AddKeys data.KeyList `json:"add_keys, omitempty"` AddKeys data.KeyList `json:"add_keys, omitempty"`
RemoveKeys []string `json:"remove_keys,omitempty"` RemoveKeys []string `json:"remove_keys,omitempty"`
AddPaths []string `json:"add_paths,omitempty"` AddPaths []string `json:"add_paths,omitempty"`
RemovePaths []string `json:"remove_paths,omitempty"` RemovePaths []string `json:"remove_paths,omitempty"`
ClearAllPaths bool `json:"clear_paths,omitempty"` ClearAllPaths bool `json:"clear_paths,omitempty"`
} }
// ToNewRole creates a fresh role object from the TUFDelegation data // ToNewRole creates a fresh role object from the TUFDelegation data
func (td TUFDelegation) ToNewRole(scope string) (*data.Role, error) { func (td TUFDelegation) ToNewRole(scope data.RoleName) (*data.Role, error) {
name := scope name := scope
if td.NewName != "" { if td.NewName != "" {
name = td.NewName name = td.NewName

View File

@ -21,6 +21,11 @@ func (cl *memChangelist) Add(c Change) error {
return nil return nil
} }
// Location returns the string "memory"
func (cl memChangelist) Location() string {
return "memory"
}
// Remove deletes the changes found at the given indices // Remove deletes the changes found at the given indices
func (cl *memChangelist) Remove(idxs []int) error { func (cl *memChangelist) Remove(idxs []int) error {
remove := make(map[int]struct{}) remove := make(map[int]struct{})

View File

@ -5,12 +5,12 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath"
"sort" "sort"
"time" "time"
"github.com/sirupsen/logrus"
"github.com/docker/distribution/uuid" "github.com/docker/distribution/uuid"
"path/filepath" "github.com/sirupsen/logrus"
) )
// FileChangelist stores all the changes as files // FileChangelist stores all the changes as files
@ -137,6 +137,11 @@ func (cl FileChangelist) Close() error {
return nil return nil
} }
// Location returns the file path to the changelist
func (cl FileChangelist) Location() string {
return cl.dir
}
// NewIterator creates an iterator from FileChangelist // NewIterator creates an iterator from FileChangelist
func (cl FileChangelist) NewIterator() (ChangeIterator, error) { func (cl FileChangelist) NewIterator() (ChangeIterator, error) {
fileInfos, err := getFileNames(cl.dir) fileInfos, err := getFileNames(cl.dir)

View File

@ -1,5 +1,7 @@
package changelist package changelist
import "github.com/docker/notary/tuf/data"
// Changelist is the interface for all TUF change lists // Changelist is the interface for all TUF change lists
type Changelist interface { type Changelist interface {
// List returns the ordered list of changes // List returns the ordered list of changes
@ -25,6 +27,9 @@ type Changelist interface {
// NewIterator returns an iterator for walking through the list // NewIterator returns an iterator for walking through the list
// of changes currently stored // of changes currently stored
NewIterator() (ChangeIterator, error) NewIterator() (ChangeIterator, error)
// Location returns the place the changelist is stores
Location() string
} }
const ( const (
@ -43,7 +48,7 @@ type Change interface {
// Where the change should be made. // Where the change should be made.
// For TUF this will be the role // For TUF this will be the role
Scope() string Scope() data.RoleName
// The content type being affected. // The content type being affected.
// For TUF this will be "target", or "delegation". // For TUF this will be "target", or "delegation".

File diff suppressed because it is too large Load Diff

View File

@ -3,19 +3,18 @@ package client
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"path/filepath"
"github.com/sirupsen/logrus"
"github.com/docker/notary" "github.com/docker/notary"
"github.com/docker/notary/client/changelist" "github.com/docker/notary/client/changelist"
store "github.com/docker/notary/storage" store "github.com/docker/notary/storage"
"github.com/docker/notary/tuf/data" "github.com/docker/notary/tuf/data"
"github.com/docker/notary/tuf/utils" "github.com/docker/notary/tuf/utils"
"github.com/sirupsen/logrus"
) )
// AddDelegation creates changelist entries to add provided delegation public keys and paths. // AddDelegation creates changelist entries to add provided delegation public keys and paths.
// This method composes AddDelegationRoleAndKeys and AddDelegationPaths (each creates one changelist if called). // This method composes AddDelegationRoleAndKeys and AddDelegationPaths (each creates one changelist if called).
func (r *NotaryRepository) AddDelegation(name string, delegationKeys []data.PublicKey, paths []string) error { func (r *repository) AddDelegation(name data.RoleName, delegationKeys []data.PublicKey, paths []string) error {
if len(delegationKeys) > 0 { if len(delegationKeys) > 0 {
err := r.AddDelegationRoleAndKeys(name, delegationKeys) err := r.AddDelegationRoleAndKeys(name, delegationKeys)
if err != nil { if err != nil {
@ -34,18 +33,12 @@ func (r *NotaryRepository) AddDelegation(name string, delegationKeys []data.Publ
// AddDelegationRoleAndKeys creates a changelist entry to add provided delegation public keys. // AddDelegationRoleAndKeys creates a changelist entry to add provided delegation public keys.
// This method is the simplest way to create a new delegation, because the delegation must have at least // This method is the simplest way to create a new delegation, because the delegation must have at least
// one key upon creation to be valid since we will reject the changelist while validating the threshold. // one key upon creation to be valid since we will reject the changelist while validating the threshold.
func (r *NotaryRepository) AddDelegationRoleAndKeys(name string, delegationKeys []data.PublicKey) error { func (r *repository) AddDelegationRoleAndKeys(name data.RoleName, delegationKeys []data.PublicKey) error {
if !data.IsDelegation(name) { if !data.IsDelegation(name) {
return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"} return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"}
} }
cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist"))
if err != nil {
return err
}
defer cl.Close()
logrus.Debugf(`Adding delegation "%s" with threshold %d, and %d keys\n`, logrus.Debugf(`Adding delegation "%s" with threshold %d, and %d keys\n`,
name, notary.MinThreshold, len(delegationKeys)) name, notary.MinThreshold, len(delegationKeys))
@ -59,23 +52,17 @@ func (r *NotaryRepository) AddDelegationRoleAndKeys(name string, delegationKeys
} }
template := newCreateDelegationChange(name, tdJSON) template := newCreateDelegationChange(name, tdJSON)
return addChange(cl, template, name) return addChange(r.changelist, template, name)
} }
// AddDelegationPaths creates a changelist entry to add provided paths to an existing delegation. // AddDelegationPaths creates a changelist entry to add provided paths to an existing delegation.
// This method cannot create a new delegation itself because the role must meet the key threshold upon creation. // This method cannot create a new delegation itself because the role must meet the key threshold upon creation.
func (r *NotaryRepository) AddDelegationPaths(name string, paths []string) error { func (r *repository) AddDelegationPaths(name data.RoleName, paths []string) error {
if !data.IsDelegation(name) { if !data.IsDelegation(name) {
return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"} return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"}
} }
cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist"))
if err != nil {
return err
}
defer cl.Close()
logrus.Debugf(`Adding %s paths to delegation %s\n`, paths, name) logrus.Debugf(`Adding %s paths to delegation %s\n`, paths, name)
tdJSON, err := json.Marshal(&changelist.TUFDelegation{ tdJSON, err := json.Marshal(&changelist.TUFDelegation{
@ -86,12 +73,12 @@ func (r *NotaryRepository) AddDelegationPaths(name string, paths []string) error
} }
template := newCreateDelegationChange(name, tdJSON) template := newCreateDelegationChange(name, tdJSON)
return addChange(cl, template, name) return addChange(r.changelist, template, name)
} }
// RemoveDelegationKeysAndPaths creates changelist entries to remove provided delegation key IDs and paths. // RemoveDelegationKeysAndPaths creates changelist entries to remove provided delegation key IDs and paths.
// This method composes RemoveDelegationPaths and RemoveDelegationKeys (each creates one changelist if called). // This method composes RemoveDelegationPaths and RemoveDelegationKeys (each creates one changelist if called).
func (r *NotaryRepository) RemoveDelegationKeysAndPaths(name string, keyIDs, paths []string) error { func (r *repository) RemoveDelegationKeysAndPaths(name data.RoleName, keyIDs, paths []string) error {
if len(paths) > 0 { if len(paths) > 0 {
err := r.RemoveDelegationPaths(name, paths) err := r.RemoveDelegationPaths(name, paths)
if err != nil { if err != nil {
@ -108,37 +95,25 @@ func (r *NotaryRepository) RemoveDelegationKeysAndPaths(name string, keyIDs, pat
} }
// RemoveDelegationRole creates a changelist to remove all paths and keys from a role, and delete the role in its entirety. // RemoveDelegationRole creates a changelist to remove all paths and keys from a role, and delete the role in its entirety.
func (r *NotaryRepository) RemoveDelegationRole(name string) error { func (r *repository) RemoveDelegationRole(name data.RoleName) error {
if !data.IsDelegation(name) { if !data.IsDelegation(name) {
return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"} return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"}
} }
cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist"))
if err != nil {
return err
}
defer cl.Close()
logrus.Debugf(`Removing delegation "%s"\n`, name) logrus.Debugf(`Removing delegation "%s"\n`, name)
template := newDeleteDelegationChange(name, nil) template := newDeleteDelegationChange(name, nil)
return addChange(cl, template, name) return addChange(r.changelist, template, name)
} }
// RemoveDelegationPaths creates a changelist entry to remove provided paths from an existing delegation. // RemoveDelegationPaths creates a changelist entry to remove provided paths from an existing delegation.
func (r *NotaryRepository) RemoveDelegationPaths(name string, paths []string) error { func (r *repository) RemoveDelegationPaths(name data.RoleName, paths []string) error {
if !data.IsDelegation(name) { if !data.IsDelegation(name) {
return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"} return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"}
} }
cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist"))
if err != nil {
return err
}
defer cl.Close()
logrus.Debugf(`Removing %s paths from delegation "%s"\n`, paths, name) logrus.Debugf(`Removing %s paths from delegation "%s"\n`, paths, name)
tdJSON, err := json.Marshal(&changelist.TUFDelegation{ tdJSON, err := json.Marshal(&changelist.TUFDelegation{
@ -149,7 +124,7 @@ func (r *NotaryRepository) RemoveDelegationPaths(name string, paths []string) er
} }
template := newUpdateDelegationChange(name, tdJSON) template := newUpdateDelegationChange(name, tdJSON)
return addChange(cl, template, name) return addChange(r.changelist, template, name)
} }
// RemoveDelegationKeys creates a changelist entry to remove provided keys from an existing delegation. // RemoveDelegationKeys creates a changelist entry to remove provided keys from an existing delegation.
@ -157,18 +132,12 @@ func (r *NotaryRepository) RemoveDelegationPaths(name string, paths []string) er
// the role itself will be deleted in its entirety. // the role itself will be deleted in its entirety.
// It can also delete a key from all delegations under a parent using a name // It can also delete a key from all delegations under a parent using a name
// with a wildcard at the end. // with a wildcard at the end.
func (r *NotaryRepository) RemoveDelegationKeys(name string, keyIDs []string) error { func (r *repository) RemoveDelegationKeys(name data.RoleName, keyIDs []string) error {
if !data.IsDelegation(name) && !data.IsWildDelegation(name) { if !data.IsDelegation(name) && !data.IsWildDelegation(name) {
return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"} return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"}
} }
cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist"))
if err != nil {
return err
}
defer cl.Close()
logrus.Debugf(`Removing %s keys from delegation "%s"\n`, keyIDs, name) logrus.Debugf(`Removing %s keys from delegation "%s"\n`, keyIDs, name)
tdJSON, err := json.Marshal(&changelist.TUFDelegation{ tdJSON, err := json.Marshal(&changelist.TUFDelegation{
@ -179,22 +148,16 @@ func (r *NotaryRepository) RemoveDelegationKeys(name string, keyIDs []string) er
} }
template := newUpdateDelegationChange(name, tdJSON) template := newUpdateDelegationChange(name, tdJSON)
return addChange(cl, template, name) return addChange(r.changelist, template, name)
} }
// ClearDelegationPaths creates a changelist entry to remove all paths from an existing delegation. // ClearDelegationPaths creates a changelist entry to remove all paths from an existing delegation.
func (r *NotaryRepository) ClearDelegationPaths(name string) error { func (r *repository) ClearDelegationPaths(name data.RoleName) error {
if !data.IsDelegation(name) { if !data.IsDelegation(name) {
return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"} return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"}
} }
cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist"))
if err != nil {
return err
}
defer cl.Close()
logrus.Debugf(`Removing all paths from delegation "%s"\n`, name) logrus.Debugf(`Removing all paths from delegation "%s"\n`, name)
tdJSON, err := json.Marshal(&changelist.TUFDelegation{ tdJSON, err := json.Marshal(&changelist.TUFDelegation{
@ -205,10 +168,10 @@ func (r *NotaryRepository) ClearDelegationPaths(name string) error {
} }
template := newUpdateDelegationChange(name, tdJSON) template := newUpdateDelegationChange(name, tdJSON)
return addChange(cl, template, name) return addChange(r.changelist, template, name)
} }
func newUpdateDelegationChange(name string, content []byte) *changelist.TUFChange { func newUpdateDelegationChange(name data.RoleName, content []byte) *changelist.TUFChange {
return changelist.NewTUFChange( return changelist.NewTUFChange(
changelist.ActionUpdate, changelist.ActionUpdate,
name, name,
@ -218,7 +181,7 @@ func newUpdateDelegationChange(name string, content []byte) *changelist.TUFChang
) )
} }
func newCreateDelegationChange(name string, content []byte) *changelist.TUFChange { func newCreateDelegationChange(name data.RoleName, content []byte) *changelist.TUFChange {
return changelist.NewTUFChange( return changelist.NewTUFChange(
changelist.ActionCreate, changelist.ActionCreate,
name, name,
@ -228,7 +191,7 @@ func newCreateDelegationChange(name string, content []byte) *changelist.TUFChang
) )
} }
func newDeleteDelegationChange(name string, content []byte) *changelist.TUFChange { func newDeleteDelegationChange(name data.RoleName, content []byte) *changelist.TUFChange {
return changelist.NewTUFChange( return changelist.NewTUFChange(
changelist.ActionDelete, changelist.ActionDelete,
name, name,
@ -240,7 +203,7 @@ func newDeleteDelegationChange(name string, content []byte) *changelist.TUFChang
// GetDelegationRoles returns the keys and roles of the repository's delegations // GetDelegationRoles returns the keys and roles of the repository's delegations
// Also converts key IDs to canonical key IDs to keep consistent with signing prompts // Also converts key IDs to canonical key IDs to keep consistent with signing prompts
func (r *NotaryRepository) GetDelegationRoles() ([]data.Role, error) { func (r *repository) GetDelegationRoles() ([]data.Role, error) {
// Update state of the repo to latest // Update state of the repo to latest
if err := r.Update(false); err != nil { if err := r.Update(false); err != nil {
return nil, err return nil, err
@ -249,7 +212,7 @@ func (r *NotaryRepository) GetDelegationRoles() ([]data.Role, error) {
// All top level delegations (ex: targets/level1) are stored exclusively in targets.json // All top level delegations (ex: targets/level1) are stored exclusively in targets.json
_, ok := r.tufRepo.Targets[data.CanonicalTargetsRole] _, ok := r.tufRepo.Targets[data.CanonicalTargetsRole]
if !ok { if !ok {
return nil, store.ErrMetaNotFound{Resource: data.CanonicalTargetsRole} return nil, store.ErrMetaNotFound{Resource: data.CanonicalTargetsRole.String()}
} }
// make a copy for traversing nested delegations // make a copy for traversing nested delegations

48
vendor/github.com/docker/notary/client/errors.go generated vendored Normal file
View File

@ -0,0 +1,48 @@
package client
import (
"fmt"
"github.com/docker/notary/tuf/data"
)
// ErrRepoNotInitialized is returned when trying to publish an uninitialized
// notary repository
type ErrRepoNotInitialized struct{}
func (err ErrRepoNotInitialized) Error() string {
return "repository has not been initialized"
}
// ErrInvalidRemoteRole is returned when the server is requested to manage
// a key type that is not permitted
type ErrInvalidRemoteRole struct {
Role data.RoleName
}
func (err ErrInvalidRemoteRole) Error() string {
return fmt.Sprintf(
"notary does not permit the server managing the %s key", err.Role.String())
}
// ErrInvalidLocalRole is returned when the client wants to manage
// a key type that is not permitted
type ErrInvalidLocalRole struct {
Role data.RoleName
}
func (err ErrInvalidLocalRole) Error() string {
return fmt.Sprintf(
"notary does not permit the client managing the %s key", err.Role)
}
// ErrRepositoryNotExist is returned when an action is taken on a remote
// repository that doesn't exist
type ErrRepositoryNotExist struct {
remote string
gun data.GUN
}
func (err ErrRepositoryNotExist) Error() string {
return fmt.Sprintf("%s does not have trust data for %s", err.remote, err.gun.String())
}

View File

@ -10,14 +10,15 @@ import (
store "github.com/docker/notary/storage" store "github.com/docker/notary/storage"
"github.com/docker/notary/tuf" "github.com/docker/notary/tuf"
"github.com/docker/notary/tuf/data" "github.com/docker/notary/tuf/data"
"github.com/docker/notary/tuf/signed"
"github.com/docker/notary/tuf/utils" "github.com/docker/notary/tuf/utils"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
// Use this to initialize remote HTTPStores from the config settings // Use this to initialize remote HTTPStores from the config settings
func getRemoteStore(baseURL, gun string, rt http.RoundTripper) (store.RemoteStore, error) { func getRemoteStore(baseURL string, gun data.GUN, rt http.RoundTripper) (store.RemoteStore, error) {
s, err := store.NewHTTPStore( s, err := store.NewHTTPStore(
baseURL+"/v2/"+gun+"/_trust/tuf/", baseURL+"/v2/"+gun.String()+"/_trust/tuf/",
"", "",
"json", "json",
"key", "key",
@ -26,7 +27,7 @@ func getRemoteStore(baseURL, gun string, rt http.RoundTripper) (store.RemoteStor
if err != nil { if err != nil {
return store.OfflineStore{}, err return store.OfflineStore{}, err
} }
return s, err return s, nil
} }
func applyChangelist(repo *tuf.Repo, invalid *tuf.Repo, cl changelist.Changelist) error { func applyChangelist(repo *tuf.Repo, invalid *tuf.Repo, cl changelist.Changelist) error {
@ -47,7 +48,7 @@ func applyChangelist(repo *tuf.Repo, invalid *tuf.Repo, cl changelist.Changelist
case c.Scope() == changelist.ScopeRoot: case c.Scope() == changelist.ScopeRoot:
err = applyRootChange(repo, c) err = applyRootChange(repo, c)
default: default:
return fmt.Errorf("scope not supported: %s", c.Scope()) return fmt.Errorf("scope not supported: %s", c.Scope().String())
} }
if err != nil { if err != nil {
logrus.Debugf("error attempting to apply change #%d: %s, on scope: %s path: %s type: %s", index, c.Action(), c.Scope(), c.Path(), c.Type()) logrus.Debugf("error attempting to apply change #%d: %s, on scope: %s path: %s type: %s", index, c.Action(), c.Scope(), c.Path(), c.Type())
@ -165,7 +166,7 @@ func changeTargetMeta(repo *tuf.Repo, c changelist.Change) error {
func applyRootChange(repo *tuf.Repo, c changelist.Change) error { func applyRootChange(repo *tuf.Repo, c changelist.Change) error {
var err error var err error
switch c.Type() { switch c.Type() {
case changelist.TypeRootRole: case changelist.TypeBaseRole:
err = applyRootRoleChange(repo, c) err = applyRootRoleChange(repo, c)
default: default:
err = fmt.Errorf("type of root change not yet supported: %s", c.Type()) err = fmt.Errorf("type of root change not yet supported: %s", c.Type())
@ -218,11 +219,7 @@ func warnRolesNearExpiry(r *tuf.Repo) {
} }
// Fetches a public key from a remote store, given a gun and role // Fetches a public key from a remote store, given a gun and role
func getRemoteKey(url, gun, role string, rt http.RoundTripper) (data.PublicKey, error) { func getRemoteKey(role data.RoleName, remote store.RemoteStore) (data.PublicKey, error) {
remote, err := getRemoteStore(url, gun, rt)
if err != nil {
return nil, err
}
rawPubKey, err := remote.GetKey(role) rawPubKey, err := remote.GetKey(role)
if err != nil { if err != nil {
return nil, err return nil, err
@ -237,11 +234,7 @@ func getRemoteKey(url, gun, role string, rt http.RoundTripper) (data.PublicKey,
} }
// Rotates a private key in a remote store and returns the public key component // Rotates a private key in a remote store and returns the public key component
func rotateRemoteKey(url, gun, role string, rt http.RoundTripper) (data.PublicKey, error) { func rotateRemoteKey(role data.RoleName, remote store.RemoteStore) (data.PublicKey, error) {
remote, err := getRemoteStore(url, gun, rt)
if err != nil {
return nil, err
}
rawPubKey, err := remote.RotateKey(role) rawPubKey, err := remote.RotateKey(role)
if err != nil { if err != nil {
return nil, err return nil, err
@ -256,11 +249,11 @@ func rotateRemoteKey(url, gun, role string, rt http.RoundTripper) (data.PublicKe
} }
// signs and serializes the metadata for a canonical role in a TUF repo to JSON // signs and serializes the metadata for a canonical role in a TUF repo to JSON
func serializeCanonicalRole(tufRepo *tuf.Repo, role string) (out []byte, err error) { func serializeCanonicalRole(tufRepo *tuf.Repo, role data.RoleName, extraSigningKeys data.KeyList) (out []byte, err error) {
var s *data.Signed var s *data.Signed
switch { switch {
case role == data.CanonicalRootRole: case role == data.CanonicalRootRole:
s, err = tufRepo.SignRoot(data.DefaultExpires(role)) s, err = tufRepo.SignRoot(data.DefaultExpires(role), extraSigningKeys)
case role == data.CanonicalSnapshotRole: case role == data.CanonicalSnapshotRole:
s, err = tufRepo.SignSnapshot(data.DefaultExpires(role)) s, err = tufRepo.SignSnapshot(data.DefaultExpires(role))
case tufRepo.Targets[role] != nil: case tufRepo.Targets[role] != nil:
@ -276,3 +269,38 @@ func serializeCanonicalRole(tufRepo *tuf.Repo, role string) (out []byte, err err
return json.Marshal(s) return json.Marshal(s)
} }
func getAllPrivKeys(rootKeyIDs []string, cryptoService signed.CryptoService) ([]data.PrivateKey, error) {
if cryptoService == nil {
return nil, fmt.Errorf("no crypto service available to get private keys from")
}
privKeys := make([]data.PrivateKey, 0, len(rootKeyIDs))
for _, keyID := range rootKeyIDs {
privKey, _, err := cryptoService.GetPrivateKey(keyID)
if err != nil {
return nil, err
}
privKeys = append(privKeys, privKey)
}
if len(privKeys) == 0 {
var rootKeyID string
rootKeyList := cryptoService.ListKeys(data.CanonicalRootRole)
if len(rootKeyList) == 0 {
rootPublicKey, err := cryptoService.Create(data.CanonicalRootRole, "", data.ECDSAKey)
if err != nil {
return nil, err
}
rootKeyID = rootPublicKey.ID()
} else {
rootKeyID = rootKeyList[0]
}
privKey, _, err := cryptoService.GetPrivateKey(rootKeyID)
if err != nil {
return nil, err
}
privKeys = append(privKeys, privKey)
}
return privKeys, nil
}

47
vendor/github.com/docker/notary/client/interface.go generated vendored Normal file
View File

@ -0,0 +1,47 @@
package client
import (
"github.com/docker/notary/client/changelist"
"github.com/docker/notary/tuf/data"
"github.com/docker/notary/tuf/signed"
)
// Repository represents the set of options that must be supported over a TUF repo.
type Repository interface {
// General management operations
Initialize(rootKeyIDs []string, serverManagedRoles ...data.RoleName) error
InitializeWithCertificate(rootKeyIDs []string, rootCerts []data.PublicKey, serverManagedRoles ...data.RoleName) error
Publish() error
// Target Operations
AddTarget(target *Target, roles ...data.RoleName) error
RemoveTarget(targetName string, roles ...data.RoleName) error
ListTargets(roles ...data.RoleName) ([]*TargetWithRole, error)
GetTargetByName(name string, roles ...data.RoleName) (*TargetWithRole, error)
GetAllTargetMetadataByName(name string) ([]TargetSignedStruct, error)
// Changelist operations
GetChangelist() (changelist.Changelist, error)
// Role operations
ListRoles() ([]RoleWithSignatures, error)
GetDelegationRoles() ([]data.Role, error)
AddDelegation(name data.RoleName, delegationKeys []data.PublicKey, paths []string) error
AddDelegationRoleAndKeys(name data.RoleName, delegationKeys []data.PublicKey) error
AddDelegationPaths(name data.RoleName, paths []string) error
RemoveDelegationKeysAndPaths(name data.RoleName, keyIDs, paths []string) error
RemoveDelegationRole(name data.RoleName) error
RemoveDelegationPaths(name data.RoleName, paths []string) error
RemoveDelegationKeys(name data.RoleName, keyIDs []string) error
ClearDelegationPaths(name data.RoleName) error
// Witness and other re-signing operations
Witness(roles ...data.RoleName) ([]data.RoleName, error)
// Key Operations
RotateKey(role data.RoleName, serverManagesKey bool, keyList []string) error
GetCryptoService() signed.CryptoService
SetLegacyVersions(int)
GetGUN() data.GUN
}

View File

@ -4,26 +4,15 @@ package client
import ( import (
"fmt" "fmt"
"net/http"
"github.com/docker/notary" "github.com/docker/notary"
"github.com/docker/notary/trustmanager" "github.com/docker/notary/trustmanager"
"github.com/docker/notary/trustpinning"
) )
// NewNotaryRepository is a helper method that returns a new notary repository. func getKeyStores(baseDir string, retriever notary.PassRetriever) ([]trustmanager.KeyStore, error) {
// It takes the base directory under where all the trust files will be stored
// (This is normally defaults to "~/.notary" or "~/.docker/trust" when enabling
// docker content trust).
func NewNotaryRepository(baseDir, gun, baseURL string, rt http.RoundTripper,
retriever notary.PassRetriever, trustPinning trustpinning.TrustPinConfig) (
*NotaryRepository, error) {
fileKeyStore, err := trustmanager.NewKeyFileStore(baseDir, retriever) fileKeyStore, err := trustmanager.NewKeyFileStore(baseDir, retriever)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create private key store in directory: %s", baseDir) return nil, fmt.Errorf("failed to create private key store in directory: %s", baseDir)
} }
return []trustmanager.KeyStore{fileKeyStore}, nil
return repositoryFromKeystores(baseDir, gun, baseURL, rt,
[]trustmanager.KeyStore{fileKeyStore}, trustPinning)
} }

View File

@ -4,21 +4,13 @@ package client
import ( import (
"fmt" "fmt"
"net/http"
"github.com/docker/notary" "github.com/docker/notary"
"github.com/docker/notary/trustmanager" "github.com/docker/notary/trustmanager"
"github.com/docker/notary/trustmanager/yubikey" "github.com/docker/notary/trustmanager/yubikey"
"github.com/docker/notary/trustpinning"
) )
// NewNotaryRepository is a helper method that returns a new notary repository. func getKeyStores(baseDir string, retriever notary.PassRetriever) ([]trustmanager.KeyStore, error) {
// It takes the base directory under where all the trust files will be stored
// (usually ~/.docker/trust/).
func NewNotaryRepository(baseDir, gun, baseURL string, rt http.RoundTripper,
retriever notary.PassRetriever, trustPinning trustpinning.TrustPinConfig) (
*NotaryRepository, error) {
fileKeyStore, err := trustmanager.NewKeyFileStore(baseDir, retriever) fileKeyStore, err := trustmanager.NewKeyFileStore(baseDir, retriever)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create private key store in directory: %s", baseDir) return nil, fmt.Errorf("failed to create private key store in directory: %s", baseDir)
@ -29,6 +21,5 @@ func NewNotaryRepository(baseDir, gun, baseURL string, rt http.RoundTripper,
if yubiKeyStore != nil { if yubiKeyStore != nil {
keyStores = []trustmanager.KeyStore{yubiKeyStore, fileKeyStore} keyStores = []trustmanager.KeyStore{yubiKeyStore, fileKeyStore}
} }
return keyStores, nil
return repositoryFromKeystores(baseDir, gun, baseURL, rt, keyStores, trustPinning)
} }

View File

@ -2,26 +2,28 @@ package client
import ( import (
"encoding/json" "encoding/json"
"fmt"
"github.com/sirupsen/logrus"
"github.com/docker/notary" "github.com/docker/notary"
store "github.com/docker/notary/storage" store "github.com/docker/notary/storage"
"github.com/docker/notary/trustpinning"
"github.com/docker/notary/tuf" "github.com/docker/notary/tuf"
"github.com/docker/notary/tuf/data" "github.com/docker/notary/tuf/data"
"github.com/docker/notary/tuf/signed" "github.com/docker/notary/tuf/signed"
"github.com/sirupsen/logrus"
) )
// TUFClient is a usability wrapper around a raw TUF repo // tufClient is a usability wrapper around a raw TUF repo
type TUFClient struct { type tufClient struct {
remote store.RemoteStore remote store.RemoteStore
cache store.MetadataStore cache store.MetadataStore
oldBuilder tuf.RepoBuilder oldBuilder tuf.RepoBuilder
newBuilder tuf.RepoBuilder newBuilder tuf.RepoBuilder
} }
// NewTUFClient initialized a TUFClient with the given repo, remote source of content, and cache // newTufClient initialized a tufClient with the given repo, remote source of content, and cache
func NewTUFClient(oldBuilder, newBuilder tuf.RepoBuilder, remote store.RemoteStore, cache store.MetadataStore) *TUFClient { func newTufClient(oldBuilder, newBuilder tuf.RepoBuilder, remote store.RemoteStore, cache store.MetadataStore) *tufClient {
return &TUFClient{ return &tufClient{
oldBuilder: oldBuilder, oldBuilder: oldBuilder,
newBuilder: newBuilder, newBuilder: newBuilder,
remote: remote, remote: remote,
@ -30,7 +32,7 @@ func NewTUFClient(oldBuilder, newBuilder tuf.RepoBuilder, remote store.RemoteSto
} }
// Update performs an update to the TUF repo as defined by the TUF spec // Update performs an update to the TUF repo as defined by the TUF spec
func (c *TUFClient) Update() (*tuf.Repo, *tuf.Repo, error) { func (c *tufClient) Update() (*tuf.Repo, *tuf.Repo, error) {
// 1. Get timestamp // 1. Get timestamp
// a. If timestamp error (verification, expired, etc...) download new root and return to 1. // a. If timestamp error (verification, expired, etc...) download new root and return to 1.
// 2. Check if local snapshot is up to date // 2. Check if local snapshot is up to date
@ -47,8 +49,8 @@ func (c *TUFClient) Update() (*tuf.Repo, *tuf.Repo, error) {
c.newBuilder = c.newBuilder.BootstrapNewBuilder() c.newBuilder = c.newBuilder.BootstrapNewBuilder()
if err := c.downloadRoot(); err != nil { if err := c.updateRoot(); err != nil {
logrus.Debug("Client Update (Root):", err) logrus.Debug("Client Update (Root): ", err)
return nil, nil, err return nil, nil, err
} }
// If we error again, we now have the latest root and just want to fail // If we error again, we now have the latest root and just want to fail
@ -61,7 +63,7 @@ func (c *TUFClient) Update() (*tuf.Repo, *tuf.Repo, error) {
return c.newBuilder.Finish() return c.newBuilder.Finish()
} }
func (c *TUFClient) update() error { func (c *tufClient) update() error {
if err := c.downloadTimestamp(); err != nil { if err := c.downloadTimestamp(); err != nil {
logrus.Debugf("Client Update (Timestamp): %s", err.Error()) logrus.Debugf("Client Update (Timestamp): %s", err.Error())
return err return err
@ -78,37 +80,103 @@ func (c *TUFClient) update() error {
return nil return nil
} }
// downloadRoot is responsible for downloading the root.json // updateRoot checks if there is a newer version of the root available, and if so
func (c *TUFClient) downloadRoot() error { // downloads all intermediate root files to allow proper key rotation.
role := data.CanonicalRootRole func (c *tufClient) updateRoot() error {
consistentInfo := c.newBuilder.GetConsistentInfo(role) // Get current root version
currentRootConsistentInfo := c.oldBuilder.GetConsistentInfo(data.CanonicalRootRole)
currentVersion := c.oldBuilder.GetLoadedVersion(currentRootConsistentInfo.RoleName)
// We can't read an exact size for the root metadata without risking getting stuck in the TUF update cycle // Get new root version
// since it's possible that downloading timestamp/snapshot metadata may fail due to a signature mismatch raw, err := c.downloadRoot()
if !consistentInfo.ChecksumKnown() {
logrus.Debugf("Loading root with no expected checksum")
// get the cached root, if it exists, just for version checking switch err.(type) {
cachedRoot, _ := c.cache.GetSized(role, -1) case *trustpinning.ErrRootRotationFail:
// prefer to download a new root // Rotation errors are okay since we haven't yet downloaded
_, remoteErr := c.tryLoadRemote(consistentInfo, cachedRoot) // all intermediate root files
return remoteErr break
case nil:
// No error updating root - we were at most 1 version behind
return nil
default:
// Return any non-rotation error.
return err
} }
_, err := c.tryLoadCacheThenRemote(consistentInfo) // Load current version into newBuilder
return err currentRaw, err := c.cache.GetSized(data.CanonicalRootRole.String(), -1)
if err != nil {
logrus.Debugf("error loading %d.%s: %s", currentVersion, data.CanonicalRootRole, err)
return err
}
if err := c.newBuilder.LoadRootForUpdate(currentRaw, currentVersion, false); err != nil {
logrus.Debugf("%d.%s is invalid: %s", currentVersion, data.CanonicalRootRole, err)
return err
}
// Extract newest version number
signedRoot := &data.Signed{}
if err := json.Unmarshal(raw, signedRoot); err != nil {
return err
}
newestRoot, err := data.RootFromSigned(signedRoot)
if err != nil {
return err
}
newestVersion := newestRoot.Signed.SignedCommon.Version
// Update from current + 1 (current already loaded) to newest - 1 (newest loaded below)
if err := c.updateRootVersions(currentVersion+1, newestVersion-1); err != nil {
return err
}
// Already downloaded newest, verify it against newest - 1
if err := c.newBuilder.LoadRootForUpdate(raw, newestVersion, true); err != nil {
logrus.Debugf("downloaded %d.%s is invalid: %s", newestVersion, data.CanonicalRootRole, err)
return err
}
logrus.Debugf("successfully verified downloaded %d.%s", newestVersion, data.CanonicalRootRole)
// Write newest to cache
if err := c.cache.Set(data.CanonicalRootRole.String(), raw); err != nil {
logrus.Debugf("unable to write %s to cache: %d.%s", newestVersion, data.CanonicalRootRole, err)
}
logrus.Debugf("finished updating root files")
return nil
}
// updateRootVersions updates the root from it's current version to a target, rotating keys
// as they are found
func (c *tufClient) updateRootVersions(fromVersion, toVersion int) error {
for v := fromVersion; v <= toVersion; v++ {
logrus.Debugf("updating root from version %d to version %d, currently fetching %d", fromVersion, toVersion, v)
versionedRole := fmt.Sprintf("%d.%s", v, data.CanonicalRootRole)
raw, err := c.remote.GetSized(versionedRole, -1)
if err != nil {
logrus.Debugf("error downloading %s: %s", versionedRole, err)
return err
}
if err := c.newBuilder.LoadRootForUpdate(raw, v, false); err != nil {
logrus.Debugf("downloaded %s is invalid: %s", versionedRole, err)
return err
}
logrus.Debugf("successfully verified downloaded %s", versionedRole)
}
return nil
} }
// downloadTimestamp is responsible for downloading the timestamp.json // downloadTimestamp is responsible for downloading the timestamp.json
// Timestamps are special in that we ALWAYS attempt to download and only // Timestamps are special in that we ALWAYS attempt to download and only
// use cache if the download fails (and the cache is still valid). // use cache if the download fails (and the cache is still valid).
func (c *TUFClient) downloadTimestamp() error { func (c *tufClient) downloadTimestamp() error {
logrus.Debug("Loading timestamp...") logrus.Debug("Loading timestamp...")
role := data.CanonicalTimestampRole role := data.CanonicalTimestampRole
consistentInfo := c.newBuilder.GetConsistentInfo(role) consistentInfo := c.newBuilder.GetConsistentInfo(role)
// always get the remote timestamp, since it supersedes the local one // always get the remote timestamp, since it supersedes the local one
cachedTS, cachedErr := c.cache.GetSized(role, notary.MaxTimestampSize) cachedTS, cachedErr := c.cache.GetSized(role.String(), notary.MaxTimestampSize)
_, remoteErr := c.tryLoadRemote(consistentInfo, cachedTS) _, remoteErr := c.tryLoadRemote(consistentInfo, cachedTS)
// check that there was no remote error, or if there was a network problem // check that there was no remote error, or if there was a network problem
@ -138,7 +206,7 @@ func (c *TUFClient) downloadTimestamp() error {
} }
// downloadSnapshot is responsible for downloading the snapshot.json // downloadSnapshot is responsible for downloading the snapshot.json
func (c *TUFClient) downloadSnapshot() error { func (c *tufClient) downloadSnapshot() error {
logrus.Debug("Loading snapshot...") logrus.Debug("Loading snapshot...")
role := data.CanonicalSnapshotRole role := data.CanonicalSnapshotRole
consistentInfo := c.newBuilder.GetConsistentInfo(role) consistentInfo := c.newBuilder.GetConsistentInfo(role)
@ -150,7 +218,7 @@ func (c *TUFClient) downloadSnapshot() error {
// downloadTargets downloads all targets and delegated targets for the repository. // downloadTargets downloads all targets and delegated targets for the repository.
// It uses a pre-order tree traversal as it's necessary to download parents first // It uses a pre-order tree traversal as it's necessary to download parents first
// to obtain the keys to validate children. // to obtain the keys to validate children.
func (c *TUFClient) downloadTargets() error { func (c *tufClient) downloadTargets() error {
toDownload := []data.DelegationRole{{ toDownload := []data.DelegationRole{{
BaseRole: data.BaseRole{Name: data.CanonicalTargetsRole}, BaseRole: data.BaseRole{Name: data.CanonicalTargetsRole},
Paths: []string{""}, Paths: []string{""},
@ -183,7 +251,7 @@ func (c *TUFClient) downloadTargets() error {
return nil return nil
} }
func (c TUFClient) getTargetsFile(role data.DelegationRole, ci tuf.ConsistentInfo) ([]data.DelegationRole, error) { func (c tufClient) getTargetsFile(role data.DelegationRole, ci tuf.ConsistentInfo) ([]data.DelegationRole, error) {
logrus.Debugf("Loading %s...", role.Name) logrus.Debugf("Loading %s...", role.Name)
tgs := &data.SignedTargets{} tgs := &data.SignedTargets{}
@ -198,8 +266,26 @@ func (c TUFClient) getTargetsFile(role data.DelegationRole, ci tuf.ConsistentInf
return tgs.GetValidDelegations(role), nil return tgs.GetValidDelegations(role), nil
} }
func (c *TUFClient) tryLoadCacheThenRemote(consistentInfo tuf.ConsistentInfo) ([]byte, error) { // downloadRoot is responsible for downloading the root.json
cachedTS, err := c.cache.GetSized(consistentInfo.RoleName, consistentInfo.Length()) func (c *tufClient) downloadRoot() ([]byte, error) {
role := data.CanonicalRootRole
consistentInfo := c.newBuilder.GetConsistentInfo(role)
// We can't read an exact size for the root metadata without risking getting stuck in the TUF update cycle
// since it's possible that downloading timestamp/snapshot metadata may fail due to a signature mismatch
if !consistentInfo.ChecksumKnown() {
logrus.Debugf("Loading root with no expected checksum")
// get the cached root, if it exists, just for version checking
cachedRoot, _ := c.cache.GetSized(role.String(), -1)
// prefer to download a new root
return c.tryLoadRemote(consistentInfo, cachedRoot)
}
return c.tryLoadCacheThenRemote(consistentInfo)
}
func (c *tufClient) tryLoadCacheThenRemote(consistentInfo tuf.ConsistentInfo) ([]byte, error) {
cachedTS, err := c.cache.GetSized(consistentInfo.RoleName.String(), consistentInfo.Length())
if err != nil { if err != nil {
logrus.Debugf("no %s in cache, must download", consistentInfo.RoleName) logrus.Debugf("no %s in cache, must download", consistentInfo.RoleName)
return c.tryLoadRemote(consistentInfo, nil) return c.tryLoadRemote(consistentInfo, nil)
@ -214,7 +300,7 @@ func (c *TUFClient) tryLoadCacheThenRemote(consistentInfo tuf.ConsistentInfo) ([
return c.tryLoadRemote(consistentInfo, cachedTS) return c.tryLoadRemote(consistentInfo, cachedTS)
} }
func (c *TUFClient) tryLoadRemote(consistentInfo tuf.ConsistentInfo, old []byte) ([]byte, error) { func (c *tufClient) tryLoadRemote(consistentInfo tuf.ConsistentInfo, old []byte) ([]byte, error) {
consistentName := consistentInfo.ConsistentName() consistentName := consistentInfo.ConsistentName()
raw, err := c.remote.GetSized(consistentName, consistentInfo.Length()) raw, err := c.remote.GetSized(consistentName, consistentInfo.Length())
if err != nil { if err != nil {
@ -232,7 +318,7 @@ func (c *TUFClient) tryLoadRemote(consistentInfo tuf.ConsistentInfo, old []byte)
return raw, err return raw, err
} }
logrus.Debugf("successfully verified downloaded %s", consistentName) logrus.Debugf("successfully verified downloaded %s", consistentName)
if err := c.cache.Set(consistentInfo.RoleName, raw); err != nil { if err := c.cache.Set(consistentInfo.RoleName.String(), raw); err != nil {
logrus.Debugf("Unable to write %s to cache: %s", consistentInfo.RoleName, err) logrus.Debugf("Unable to write %s to cache: %s", consistentInfo.RoleName, err)
} }
return raw, nil return raw, nil

View File

@ -1,8 +1,6 @@
package client package client
import ( import (
"path/filepath"
"github.com/docker/notary/client/changelist" "github.com/docker/notary/client/changelist"
"github.com/docker/notary/tuf" "github.com/docker/notary/tuf"
"github.com/docker/notary/tuf/data" "github.com/docker/notary/tuf/data"
@ -10,14 +8,9 @@ import (
// Witness creates change objects to witness (i.e. re-sign) the given // Witness creates change objects to witness (i.e. re-sign) the given
// roles on the next publish. One change is created per role // roles on the next publish. One change is created per role
func (r *NotaryRepository) Witness(roles ...string) ([]string, error) { func (r *repository) Witness(roles ...data.RoleName) ([]data.RoleName, error) {
cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist")) var err error
if err != nil { successful := make([]data.RoleName, 0, len(roles))
return nil, err
}
defer cl.Close()
successful := make([]string, 0, len(roles))
for _, role := range roles { for _, role := range roles {
// scope is role // scope is role
c := changelist.NewTUFChange( c := changelist.NewTUFChange(
@ -27,7 +20,7 @@ func (r *NotaryRepository) Witness(roles ...string) ([]string, error) {
"", "",
nil, nil,
) )
err = cl.Add(c) err = r.changelist.Add(c)
if err != nil { if err != nil {
break break
} }
@ -36,7 +29,7 @@ func (r *NotaryRepository) Witness(roles ...string) ([]string, error) {
return successful, err return successful, err
} }
func witnessTargets(repo *tuf.Repo, invalid *tuf.Repo, role string) error { func witnessTargets(repo *tuf.Repo, invalid *tuf.Repo, role data.RoleName) error {
if r, ok := repo.Targets[role]; ok { if r, ok := repo.Targets[role]; ok {
// role is already valid, mark for re-signing/updating // role is already valid, mark for re-signing/updating
r.Dirty = true r.Dirty = true

View File

@ -1,6 +1,8 @@
package notary package notary
import "time" import (
"time"
)
// application wide constants // application wide constants
const ( const (
@ -12,14 +14,10 @@ const (
MinRSABitSize = 2048 MinRSABitSize = 2048
// MinThreshold requires a minimum of one threshold for roles; currently we do not support a higher threshold // MinThreshold requires a minimum of one threshold for roles; currently we do not support a higher threshold
MinThreshold = 1 MinThreshold = 1
// PrivKeyPerms are the file permissions to use when writing private keys to disk // SHA256HexSize is how big a SHA256 hex is in number of characters
PrivKeyPerms = 0700 SHA256HexSize = 64
// PubCertPerms are the file permissions to use when writing public certificates to disk // SHA512HexSize is how big a SHA512 hex is in number of characters
PubCertPerms = 0755 SHA512HexSize = 128
// Sha256HexSize is how big a Sha256 hex is in number of characters
Sha256HexSize = 64
// Sha512HexSize is how big a Sha512 hex is in number of characters
Sha512HexSize = 128
// SHA256 is the name of SHA256 hash algorithm // SHA256 is the name of SHA256 hash algorithm
SHA256 = "sha256" SHA256 = "sha256"
// SHA512 is the name of SHA512 hash algorithm // SHA512 is the name of SHA512 hash algorithm
@ -29,8 +27,10 @@ const (
// PrivDir is the directory, under the notary repo base directory, where private keys are stored // PrivDir is the directory, under the notary repo base directory, where private keys are stored
PrivDir = "private" PrivDir = "private"
// RootKeysSubdir is the subdirectory under PrivDir where root private keys are stored // RootKeysSubdir is the subdirectory under PrivDir where root private keys are stored
// DEPRECATED: The only reason we need this constant is compatibility with older versions
RootKeysSubdir = "root_keys" RootKeysSubdir = "root_keys"
// NonRootKeysSubdir is the subdirectory under PrivDir where non-root private keys are stored // NonRootKeysSubdir is the subdirectory under PrivDir where non-root private keys are stored
// DEPRECATED: The only reason we need this constant is compatibility with older versions
NonRootKeysSubdir = "tuf_keys" NonRootKeysSubdir = "tuf_keys"
// KeyExtension is the file extension to use for private key files // KeyExtension is the file extension to use for private key files
KeyExtension = "key" KeyExtension = "key"
@ -54,17 +54,42 @@ const (
MySQLBackend = "mysql" MySQLBackend = "mysql"
MemoryBackend = "memory" MemoryBackend = "memory"
PostgresBackend = "postgres"
SQLiteBackend = "sqlite3" SQLiteBackend = "sqlite3"
RethinkDBBackend = "rethinkdb" RethinkDBBackend = "rethinkdb"
FileBackend = "file"
DefaultImportRole = "delegation" DefaultImportRole = "delegation"
// HealthCheckKeyManagement and HealthCheckSigner are the grpc service name
// for "KeyManagement" and "Signer" respectively which used for health check.
// The "Overall" indicates the querying for overall status of the server.
HealthCheckKeyManagement = "grpc.health.v1.Health.KeyManagement"
HealthCheckSigner = "grpc.health.v1.Health.Signer"
HealthCheckOverall = "grpc.health.v1.Health.Overall"
// PrivExecPerms indicates the file permissions for directory
// and PrivNoExecPerms for file.
PrivExecPerms = 0700
PrivNoExecPerms = 0600
// DefaultPageSize is the default number of records to return from the changefeed
DefaultPageSize = 100
) )
// NotaryDefaultExpiries is the construct used to configure the default expiry times of // enum to use for setting and retrieving values from contexts
// the various role files. const (
var NotaryDefaultExpiries = map[string]time.Duration{ CtxKeyMetaStore CtxKey = iota
"root": NotaryRootExpiry, CtxKeyKeyAlgo
"targets": NotaryTargetsExpiry, CtxKeyCryptoSvc
"snapshot": NotarySnapshotExpiry, CtxKeyRepo
"timestamp": NotaryTimestampExpiry, )
// NotarySupportedBackends contains the backends we would like to support at present
var NotarySupportedBackends = []string{
MemoryBackend,
MySQLBackend,
SQLiteBackend,
RethinkDBBackend,
PostgresBackend,
} }

View File

@ -12,17 +12,17 @@ import (
) )
// GenerateCertificate generates an X509 Certificate from a template, given a GUN and validity interval // GenerateCertificate generates an X509 Certificate from a template, given a GUN and validity interval
func GenerateCertificate(rootKey data.PrivateKey, gun string, startTime, endTime time.Time) (*x509.Certificate, error) { func GenerateCertificate(rootKey data.PrivateKey, gun data.GUN, startTime, endTime time.Time) (*x509.Certificate, error) {
signer := rootKey.CryptoSigner() signer := rootKey.CryptoSigner()
if signer == nil { if signer == nil {
return nil, fmt.Errorf("key type not supported for Certificate generation: %s\n", rootKey.Algorithm()) return nil, fmt.Errorf("key type not supported for Certificate generation: %s", rootKey.Algorithm())
} }
return generateCertificate(signer, gun, startTime, endTime) return generateCertificate(signer, gun, startTime, endTime)
} }
func generateCertificate(signer crypto.Signer, gun string, startTime, endTime time.Time) (*x509.Certificate, error) { func generateCertificate(signer crypto.Signer, gun data.GUN, startTime, endTime time.Time) (*x509.Certificate, error) {
template, err := utils.NewCertificate(gun, startTime, endTime) template, err := utils.NewCertificate(gun.String(), startTime, endTime)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create the certificate template for: %s (%v)", gun, err) return nil, fmt.Errorf("failed to create the certificate template for: %s (%v)", gun, err)
} }

View File

@ -1,17 +1,16 @@
package cryptoservice package cryptoservice
import ( import (
"crypto/rand"
"fmt"
"crypto/x509" "crypto/x509"
"encoding/pem" "encoding/pem"
"errors" "errors"
"github.com/sirupsen/logrus" "fmt"
"github.com/docker/notary" "github.com/docker/notary"
"github.com/docker/notary/trustmanager" "github.com/docker/notary/trustmanager"
"github.com/docker/notary/tuf/data" "github.com/docker/notary/tuf/data"
"github.com/docker/notary/tuf/utils" "github.com/docker/notary/tuf/utils"
"github.com/sirupsen/logrus"
) )
var ( var (
@ -36,47 +35,23 @@ func NewCryptoService(keyStores ...trustmanager.KeyStore) *CryptoService {
} }
// Create is used to generate keys for targets, snapshots and timestamps // Create is used to generate keys for targets, snapshots and timestamps
func (cs *CryptoService) Create(role, gun, algorithm string) (data.PublicKey, error) { func (cs *CryptoService) Create(role data.RoleName, gun data.GUN, algorithm string) (data.PublicKey, error) {
var privKey data.PrivateKey if algorithm == data.RSAKey {
var err error return nil, fmt.Errorf("%s keys can only be imported", data.RSAKey)
switch algorithm {
case data.RSAKey:
privKey, err = utils.GenerateRSAKey(rand.Reader, notary.MinRSABitSize)
if err != nil {
return nil, fmt.Errorf("failed to generate RSA key: %v", err)
}
case data.ECDSAKey:
privKey, err = utils.GenerateECDSAKey(rand.Reader)
if err != nil {
return nil, fmt.Errorf("failed to generate EC key: %v", err)
}
case data.ED25519Key:
privKey, err = utils.GenerateED25519Key(rand.Reader)
if err != nil {
return nil, fmt.Errorf("failed to generate ED25519 key: %v", err)
}
default:
return nil, fmt.Errorf("private key type not supported for key generation: %s", algorithm)
} }
logrus.Debugf("generated new %s key for role: %s and keyID: %s", algorithm, role, privKey.ID())
// Store the private key into our keystore privKey, err := utils.GenerateKey(algorithm)
for _, ks := range cs.keyStores {
err = ks.AddKey(trustmanager.KeyInfo{Role: role, Gun: gun}, privKey)
if err == nil {
return data.PublicKeyFromPrivate(privKey), nil
}
}
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to add key to filestore: %v", err) return nil, fmt.Errorf("failed to generate %s key: %v", algorithm, err)
} }
logrus.Debugf("generated new %s key for role: %s and keyID: %s", algorithm, role.String(), privKey.ID())
pubKey := data.PublicKeyFromPrivate(privKey)
return nil, fmt.Errorf("keystores would not accept new private keys for unknown reasons") return pubKey, cs.AddKey(role, gun, privKey)
} }
// GetPrivateKey returns a private key and role if present by ID. // GetPrivateKey returns a private key and role if present by ID.
func (cs *CryptoService) GetPrivateKey(keyID string) (k data.PrivateKey, role string, err error) { func (cs *CryptoService) GetPrivateKey(keyID string) (k data.PrivateKey, role data.RoleName, err error) {
for _, ks := range cs.keyStores { for _, ks := range cs.keyStores {
if k, role, err = ks.GetKey(keyID); err == nil { if k, role, err = ks.GetKey(keyID); err == nil {
return return
@ -120,14 +95,14 @@ func (cs *CryptoService) RemoveKey(keyID string) (err error) {
// AddKey adds a private key to a specified role. // AddKey adds a private key to a specified role.
// The GUN is inferred from the cryptoservice itself for non-root roles // The GUN is inferred from the cryptoservice itself for non-root roles
func (cs *CryptoService) AddKey(role, gun string, key data.PrivateKey) (err error) { func (cs *CryptoService) AddKey(role data.RoleName, gun data.GUN, key data.PrivateKey) (err error) {
// First check if this key already exists in any of our keystores // First check if this key already exists in any of our keystores
for _, ks := range cs.keyStores { for _, ks := range cs.keyStores {
if keyInfo, err := ks.GetKeyInfo(key.ID()); err == nil { if keyInfo, err := ks.GetKeyInfo(key.ID()); err == nil {
if keyInfo.Role != role { if keyInfo.Role != role {
return fmt.Errorf("key with same ID already exists for role: %s", keyInfo.Role) return fmt.Errorf("key with same ID already exists for role: %s", keyInfo.Role.String())
} }
logrus.Debugf("key with same ID %s and role %s already exists", key.ID(), keyInfo.Role) logrus.Debugf("key with same ID %s and role %s already exists", key.ID(), keyInfo.Role.String())
return nil return nil
} }
} }
@ -142,7 +117,7 @@ func (cs *CryptoService) AddKey(role, gun string, key data.PrivateKey) (err erro
} }
// ListKeys returns a list of key IDs valid for the given role // ListKeys returns a list of key IDs valid for the given role
func (cs *CryptoService) ListKeys(role string) []string { func (cs *CryptoService) ListKeys(role data.RoleName) []string {
var res []string var res []string
for _, ks := range cs.keyStores { for _, ks := range cs.keyStores {
for k, r := range ks.ListKeys() { for k, r := range ks.ListKeys() {
@ -155,8 +130,8 @@ func (cs *CryptoService) ListKeys(role string) []string {
} }
// ListAllKeys returns a map of key IDs to role // ListAllKeys returns a map of key IDs to role
func (cs *CryptoService) ListAllKeys() map[string]string { func (cs *CryptoService) ListAllKeys() map[string]data.RoleName {
res := make(map[string]string) res := make(map[string]data.RoleName)
for _, ks := range cs.keyStores { for _, ks := range cs.keyStores {
for k, r := range ks.ListKeys() { for k, r := range ks.ListKeys() {
res[k] = r.Role // keys are content addressed so don't care about overwrites res[k] = r.Role // keys are content addressed so don't care about overwrites
@ -173,9 +148,12 @@ func CheckRootKeyIsEncrypted(pemBytes []byte) error {
return ErrNoValidPrivateKey return ErrNoValidPrivateKey
} }
if !x509.IsEncryptedPEMBlock(block) { if block.Type == "ENCRYPTED PRIVATE KEY" {
return ErrRootKeyNotEncrypted return nil
}
if !notary.FIPSEnabled() && x509.IsEncryptedPEMBlock(block) {
return nil
} }
return nil return ErrRootKeyNotEncrypted
} }

13
vendor/github.com/docker/notary/fips.go generated vendored Normal file
View File

@ -0,0 +1,13 @@
package notary
import "os"
// FIPSEnvVar is the name of the environment variable that is being used to switch
// between FIPS and non-FIPS mode
const FIPSEnvVar = "GOFIPS"
// FIPSEnabled returns true if environment variable `GOFIPS` has been set to enable
// FIPS mode
func FIPSEnabled() bool {
return os.Getenv(FIPSEnvVar) != ""
}

View File

@ -5,3 +5,8 @@ package notary
// confirmation), createNew will be true. Attempts is passed in so that implementers // confirmation), createNew will be true. Attempts is passed in so that implementers
// decide how many chances to give to a human, for example. // decide how many chances to give to a human, for example.
type PassRetriever func(keyName, alias string, createNew bool, attempts int) (passphrase string, giveup bool, err error) type PassRetriever func(keyName, alias string, createNew bool, attempts int) (passphrase string, giveup bool, err error)
// CtxKey is a wrapper type for use in context.WithValue() to satisfy golint
// https://github.com/golang/go/issues/17293
// https://github.com/golang/lint/pull/245
type CtxKey int

View File

@ -11,15 +11,13 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/docker/docker/pkg/term"
"github.com/docker/notary" "github.com/docker/notary"
"golang.org/x/crypto/ssh/terminal"
) )
const ( const (
idBytesToDisplay = 7 idBytesToDisplay = 7
tufRootAlias = "root" tufRootAlias = "root"
tufTargetsAlias = "targets"
tufSnapshotAlias = "snapshot"
tufRootKeyGenerationWarning = `You are about to create a new root signing key passphrase. This passphrase tufRootKeyGenerationWarning = `You are about to create a new root signing key passphrase. This passphrase
will be used to protect the most sensitive key in your signing system. Please will be used to protect the most sensitive key in your signing system. Please
choose a long, complex passphrase and be careful to keep the password and the choose a long, complex passphrase and be careful to keep the password and the
@ -51,7 +49,7 @@ var (
// Upon successful passphrase retrievals, the passphrase will be cached such that // Upon successful passphrase retrievals, the passphrase will be cached such that
// subsequent prompts will produce the same passphrase. // subsequent prompts will produce the same passphrase.
func PromptRetriever() notary.PassRetriever { func PromptRetriever() notary.PassRetriever {
if !term.IsTerminal(os.Stdin.Fd()) { if !terminal.IsTerminal(int(os.Stdin.Fd())) {
return func(string, string, bool, int) (string, bool, error) { return func(string, string, bool, int) (string, bool, error) {
return "", false, ErrNoInput return "", false, ErrNoInput
} }
@ -93,17 +91,6 @@ func (br *boundRetriever) requestPassphrase(keyName, alias string, createNew boo
displayAlias = val displayAlias = val
} }
// If typing on the terminal, we do not want the terminal to echo the
// password that is typed (so it doesn't display)
if term.IsTerminal(os.Stdin.Fd()) {
state, err := term.SaveState(os.Stdin.Fd())
if err != nil {
return "", false, err
}
term.DisableEcho(os.Stdin.Fd(), state)
defer term.RestoreTerminal(os.Stdin.Fd(), state)
}
indexOfLastSeparator := strings.LastIndex(keyName, string(filepath.Separator)) indexOfLastSeparator := strings.LastIndex(keyName, string(filepath.Separator))
if indexOfLastSeparator == -1 { if indexOfLastSeparator == -1 {
indexOfLastSeparator = 0 indexOfLastSeparator = 0
@ -135,7 +122,7 @@ func (br *boundRetriever) requestPassphrase(keyName, alias string, createNew boo
} }
stdin := bufio.NewReader(br.in) stdin := bufio.NewReader(br.in)
passphrase, err := stdin.ReadBytes('\n') passphrase, err := GetPassphrase(stdin)
fmt.Fprintln(br.out) fmt.Fprintln(br.out)
if err != nil { if err != nil {
return "", false, err return "", false, err
@ -162,7 +149,8 @@ func (br *boundRetriever) verifyAndConfirmPassword(stdin *bufio.Reader, retPass,
} }
fmt.Fprintf(br.out, "Repeat passphrase for new %s key%s: ", displayAlias, withID) fmt.Fprintf(br.out, "Repeat passphrase for new %s key%s: ", displayAlias, withID)
confirmation, err := stdin.ReadBytes('\n')
confirmation, err := GetPassphrase(stdin)
fmt.Fprintln(br.out) fmt.Fprintln(br.out)
if err != nil { if err != nil {
return err return err
@ -203,3 +191,20 @@ func ConstantRetriever(constantPassphrase string) notary.PassRetriever {
return constantPassphrase, false, nil return constantPassphrase, false, nil
} }
} }
// GetPassphrase get the passphrase from bufio.Reader or from terminal.
// If typing on the terminal, we disable terminal to echo the passphrase.
func GetPassphrase(in *bufio.Reader) ([]byte, error) {
var (
passphrase []byte
err error
)
if terminal.IsTerminal(int(os.Stdin.Fd())) {
passphrase, err = terminal.ReadPassword(int(os.Stdin.Fd()))
} else {
passphrase, err = in.ReadBytes('\n')
}
return passphrase, err
}

View File

@ -1,6 +1,8 @@
package storage package storage
import ( import (
"bytes"
"encoding/pem"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
@ -9,19 +11,13 @@ import (
"strings" "strings"
"github.com/docker/notary" "github.com/docker/notary"
"github.com/sirupsen/logrus"
) )
// NewFilesystemStore creates a new store in a directory tree
func NewFilesystemStore(baseDir, subDir, extension string) (*FilesystemStore, error) {
baseDir = filepath.Join(baseDir, subDir)
return NewFileStore(baseDir, extension, notary.PrivKeyPerms)
}
// NewFileStore creates a fully configurable file store // NewFileStore creates a fully configurable file store
func NewFileStore(baseDir, fileExt string, perms os.FileMode) (*FilesystemStore, error) { func NewFileStore(baseDir, fileExt string) (*FilesystemStore, error) {
baseDir = filepath.Clean(baseDir) baseDir = filepath.Clean(baseDir)
if err := createDirectory(baseDir, perms); err != nil { if err := createDirectory(baseDir, notary.PrivExecPerms); err != nil {
return nil, err return nil, err
} }
if !strings.HasPrefix(fileExt, ".") { if !strings.HasPrefix(fileExt, ".") {
@ -31,34 +27,95 @@ func NewFileStore(baseDir, fileExt string, perms os.FileMode) (*FilesystemStore,
return &FilesystemStore{ return &FilesystemStore{
baseDir: baseDir, baseDir: baseDir,
ext: fileExt, ext: fileExt,
perms: perms,
}, nil }, nil
} }
// NewSimpleFileStore is a convenience wrapper to create a world readable,
// owner writeable filestore
func NewSimpleFileStore(baseDir, fileExt string) (*FilesystemStore, error) {
return NewFileStore(baseDir, fileExt, notary.PubCertPerms)
}
// NewPrivateKeyFileStorage initializes a new filestore for private keys, appending // NewPrivateKeyFileStorage initializes a new filestore for private keys, appending
// the notary.PrivDir to the baseDir. // the notary.PrivDir to the baseDir.
func NewPrivateKeyFileStorage(baseDir, fileExt string) (*FilesystemStore, error) { func NewPrivateKeyFileStorage(baseDir, fileExt string) (*FilesystemStore, error) {
baseDir = filepath.Join(baseDir, notary.PrivDir) baseDir = filepath.Join(baseDir, notary.PrivDir)
return NewFileStore(baseDir, fileExt, notary.PrivKeyPerms) myStore, err := NewFileStore(baseDir, fileExt)
myStore.migrateTo0Dot4()
return myStore, err
} }
// NewPrivateSimpleFileStore is a wrapper to create an owner readable/writeable // NewPrivateSimpleFileStore is a wrapper to create an owner readable/writeable
// _only_ filestore // _only_ filestore
func NewPrivateSimpleFileStore(baseDir, fileExt string) (*FilesystemStore, error) { func NewPrivateSimpleFileStore(baseDir, fileExt string) (*FilesystemStore, error) {
return NewFileStore(baseDir, fileExt, notary.PrivKeyPerms) return NewFileStore(baseDir, fileExt)
} }
// FilesystemStore is a store in a locally accessible directory // FilesystemStore is a store in a locally accessible directory
type FilesystemStore struct { type FilesystemStore struct {
baseDir string baseDir string
ext string ext string
perms os.FileMode }
func (f *FilesystemStore) moveKeyTo0Dot4Location(file string) {
keyID := filepath.Base(file)
fileDir := filepath.Dir(file)
d, _ := f.Get(file)
block, _ := pem.Decode(d)
if block == nil {
logrus.Warn("Key data for", file, "could not be decoded as a valid PEM block. The key will not been migrated and may not be available")
return
}
fileDir = strings.TrimPrefix(fileDir, notary.RootKeysSubdir)
fileDir = strings.TrimPrefix(fileDir, notary.NonRootKeysSubdir)
if fileDir != "" {
block.Headers["gun"] = filepath.ToSlash(fileDir[1:])
}
if strings.Contains(keyID, "_") {
role := strings.Split(keyID, "_")[1]
keyID = strings.TrimSuffix(keyID, "_"+role)
block.Headers["role"] = role
}
var keyPEM bytes.Buffer
// since block came from decoding the PEM bytes in the first place, and all we're doing is adding some headers we ignore the possibility of an error while encoding the block
pem.Encode(&keyPEM, block)
f.Set(keyID, keyPEM.Bytes())
}
func (f *FilesystemStore) migrateTo0Dot4() {
rootKeysSubDir := filepath.Clean(filepath.Join(f.Location(), notary.RootKeysSubdir))
nonRootKeysSubDir := filepath.Clean(filepath.Join(f.Location(), notary.NonRootKeysSubdir))
if _, err := os.Stat(rootKeysSubDir); !os.IsNotExist(err) && f.Location() != rootKeysSubDir {
if rootKeysSubDir == "" || rootKeysSubDir == "/" {
// making sure we don't remove a user's homedir
logrus.Warn("The directory for root keys is an unsafe value, we are not going to delete the directory. Please delete it manually")
} else {
// root_keys exists, migrate things from it
listOnlyRootKeysDirStore, _ := NewFileStore(rootKeysSubDir, f.ext)
for _, file := range listOnlyRootKeysDirStore.ListFiles() {
f.moveKeyTo0Dot4Location(filepath.Join(notary.RootKeysSubdir, file))
}
// delete the old directory
os.RemoveAll(rootKeysSubDir)
}
}
if _, err := os.Stat(nonRootKeysSubDir); !os.IsNotExist(err) && f.Location() != nonRootKeysSubDir {
if nonRootKeysSubDir == "" || nonRootKeysSubDir == "/" {
// making sure we don't remove a user's homedir
logrus.Warn("The directory for non root keys is an unsafe value, we are not going to delete the directory. Please delete it manually")
} else {
// tuf_keys exists, migrate things from it
listOnlyNonRootKeysDirStore, _ := NewFileStore(nonRootKeysSubDir, f.ext)
for _, file := range listOnlyNonRootKeysDirStore.ListFiles() {
f.moveKeyTo0Dot4Location(filepath.Join(notary.NonRootKeysSubdir, file))
}
// delete the old directory
os.RemoveAll(nonRootKeysSubDir)
}
}
// if we have a trusted_certificates folder, let's delete for a complete migration since it is unused by new clients
certsSubDir := filepath.Join(f.Location(), "trusted_certificates")
if certsSubDir == "" || certsSubDir == "/" {
logrus.Warn("The directory for trusted certificate is an unsafe value, we are not going to delete the directory. Please delete it manually")
} else {
os.RemoveAll(certsSubDir)
}
} }
func (f *FilesystemStore) getPath(name string) (string, error) { func (f *FilesystemStore) getPath(name string) (string, error) {
@ -80,7 +137,7 @@ func (f *FilesystemStore) GetSized(name string, size int64) ([]byte, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
file, err := os.OpenFile(p, os.O_RDONLY, f.perms) file, err := os.OpenFile(p, os.O_RDONLY, notary.PrivNoExecPerms)
if err != nil { if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
err = ErrMetaNotFound{Resource: name} err = ErrMetaNotFound{Resource: name}
@ -140,7 +197,7 @@ func (f *FilesystemStore) Set(name string, meta []byte) error {
} }
// Ensures the parent directories of the file we are about to write exist // Ensures the parent directories of the file we are about to write exist
err = os.MkdirAll(filepath.Dir(fp), f.perms) err = os.MkdirAll(filepath.Dir(fp), notary.PrivExecPerms)
if err != nil { if err != nil {
return err return err
} }
@ -149,7 +206,7 @@ func (f *FilesystemStore) Set(name string, meta []byte) error {
os.RemoveAll(fp) os.RemoveAll(fp)
// Write the file to disk // Write the file to disk
if err = ioutil.WriteFile(fp, meta, f.perms); err != nil { if err = ioutil.WriteFile(fp, meta, notary.PrivNoExecPerms); err != nil {
return err return err
} }
return nil return nil

View File

@ -22,9 +22,17 @@ import (
"net/url" "net/url"
"path" "path"
"github.com/sirupsen/logrus"
"github.com/docker/notary" "github.com/docker/notary"
"github.com/docker/notary/tuf/data"
"github.com/docker/notary/tuf/validation" "github.com/docker/notary/tuf/validation"
"github.com/sirupsen/logrus"
)
const (
// MaxErrorResponseSize is the maximum size for an error message - 1KiB
MaxErrorResponseSize int64 = 1 << 10
// MaxKeySize is the maximum size for a stored TUF key - 256KiB
MaxKeySize = 256 << 10
) )
// ErrServerUnavailable indicates an error from the server. code allows us to // ErrServerUnavailable indicates an error from the server. code allows us to
@ -39,6 +47,21 @@ type NetworkError struct {
} }
func (n NetworkError) Error() string { func (n NetworkError) Error() string {
if _, ok := n.Wrapped.(*url.Error); ok {
// QueryUnescape does the inverse transformation of QueryEscape,
// converting %AB into the byte 0xAB and '+' into ' ' (space).
// It returns an error if any % is not followed by two hexadecimal digits.
//
// If this happens, we log out the QueryUnescape error and return the
// original error to client.
res, err := url.QueryUnescape(n.Wrapped.Error())
if err != nil {
logrus.Errorf("unescape network error message failed: %s", err)
return n.Wrapped.Error()
}
return res
}
return n.Wrapped.Error() return n.Wrapped.Error()
} }
@ -88,7 +111,9 @@ type HTTPStore struct {
roundTrip http.RoundTripper roundTrip http.RoundTripper
} }
// NewHTTPStore initializes a new store against a URL and a number of configuration options // NewHTTPStore initializes a new store against a URL and a number of configuration options.
//
// In case of a nil `roundTrip`, a default offline store is used instead.
func NewHTTPStore(baseURL, metaPrefix, metaExtension, keyExtension string, roundTrip http.RoundTripper) (RemoteStore, error) { func NewHTTPStore(baseURL, metaPrefix, metaExtension, keyExtension string, roundTrip http.RoundTripper) (RemoteStore, error) {
base, err := url.Parse(baseURL) base, err := url.Parse(baseURL)
if err != nil { if err != nil {
@ -110,7 +135,8 @@ func NewHTTPStore(baseURL, metaPrefix, metaExtension, keyExtension string, round
} }
func tryUnmarshalError(resp *http.Response, defaultError error) error { func tryUnmarshalError(resp *http.Response, defaultError error) error {
bodyBytes, err := ioutil.ReadAll(resp.Body) b := io.LimitReader(resp.Body, MaxErrorResponseSize)
bodyBytes, err := ioutil.ReadAll(b)
if err != nil { if err != nil {
return defaultError return defaultError
} }
@ -269,8 +295,8 @@ func (s HTTPStore) buildMetaURL(name string) (*url.URL, error) {
return s.buildURL(uri) return s.buildURL(uri)
} }
func (s HTTPStore) buildKeyURL(name string) (*url.URL, error) { func (s HTTPStore) buildKeyURL(name data.RoleName) (*url.URL, error) {
filename := fmt.Sprintf("%s.%s", name, s.keyExtension) filename := fmt.Sprintf("%s.%s", name.String(), s.keyExtension)
uri := path.Join(s.metaPrefix, filename) uri := path.Join(s.metaPrefix, filename)
return s.buildURL(uri) return s.buildURL(uri)
} }
@ -284,7 +310,7 @@ func (s HTTPStore) buildURL(uri string) (*url.URL, error) {
} }
// GetKey retrieves a public key from the remote server // GetKey retrieves a public key from the remote server
func (s HTTPStore) GetKey(role string) ([]byte, error) { func (s HTTPStore) GetKey(role data.RoleName) ([]byte, error) {
url, err := s.buildKeyURL(role) url, err := s.buildKeyURL(role)
if err != nil { if err != nil {
return nil, err return nil, err
@ -298,10 +324,11 @@ func (s HTTPStore) GetKey(role string) ([]byte, error) {
return nil, NetworkError{Wrapped: err} return nil, NetworkError{Wrapped: err}
} }
defer resp.Body.Close() defer resp.Body.Close()
if err := translateStatusToError(resp, role+" key"); err != nil { if err := translateStatusToError(resp, role.String()+" key"); err != nil {
return nil, err return nil, err
} }
body, err := ioutil.ReadAll(resp.Body) b := io.LimitReader(resp.Body, MaxKeySize)
body, err := ioutil.ReadAll(b)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -309,7 +336,7 @@ func (s HTTPStore) GetKey(role string) ([]byte, error) {
} }
// RotateKey rotates a private key and returns the public component from the remote server // RotateKey rotates a private key and returns the public component from the remote server
func (s HTTPStore) RotateKey(role string) ([]byte, error) { func (s HTTPStore) RotateKey(role data.RoleName) ([]byte, error) {
url, err := s.buildKeyURL(role) url, err := s.buildKeyURL(role)
if err != nil { if err != nil {
return nil, err return nil, err
@ -323,10 +350,11 @@ func (s HTTPStore) RotateKey(role string) ([]byte, error) {
return nil, NetworkError{Wrapped: err} return nil, NetworkError{Wrapped: err}
} }
defer resp.Body.Close() defer resp.Body.Close()
if err := translateStatusToError(resp, role+" key"); err != nil { if err := translateStatusToError(resp, role.String()+" key"); err != nil {
return nil, err return nil, err
} }
body, err := ioutil.ReadAll(resp.Body) b := io.LimitReader(resp.Body, MaxKeySize)
body, err := ioutil.ReadAll(b)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -1,5 +1,9 @@
package storage package storage
import (
"github.com/docker/notary/tuf/data"
)
// NoSizeLimit is represented as -1 for arguments to GetMeta // NoSizeLimit is represented as -1 for arguments to GetMeta
const NoSizeLimit int64 = -1 const NoSizeLimit int64 = -1
@ -15,8 +19,8 @@ type MetadataStore interface {
// PublicKeyStore must be implemented by a key service // PublicKeyStore must be implemented by a key service
type PublicKeyStore interface { type PublicKeyStore interface {
GetKey(role string) ([]byte, error) GetKey(role data.RoleName) ([]byte, error)
RotateKey(role string) ([]byte, error) RotateKey(role data.RoleName) ([]byte, error)
} }
// RemoteStore is similar to LocalStore with the added expectation that it should // RemoteStore is similar to LocalStore with the added expectation that it should

View File

@ -2,25 +2,29 @@ package storage
import ( import (
"crypto/sha256" "crypto/sha256"
"encoding/json"
"fmt"
"github.com/docker/notary" "github.com/docker/notary"
"github.com/docker/notary/tuf/data"
"github.com/docker/notary/tuf/utils" "github.com/docker/notary/tuf/utils"
) )
// NewMemoryStore returns a MetadataStore that operates entirely in memory. // NewMemoryStore returns a MetadataStore that operates entirely in memory.
// Very useful for testing // Very useful for testing
func NewMemoryStore(initial map[string][]byte) *MemoryStore { func NewMemoryStore(seed map[data.RoleName][]byte) *MemoryStore {
var consistent = make(map[string][]byte) var (
if initial == nil { consistent = make(map[string][]byte)
initial = make(map[string][]byte) initial = make(map[string][]byte)
} else { )
// add all seed meta to consistent // add all seed meta to consistent
for name, data := range initial { for name, d := range seed {
checksum := sha256.Sum256(data) checksum := sha256.Sum256(d)
path := utils.ConsistentName(name, checksum[:]) path := utils.ConsistentName(name.String(), checksum[:])
consistent[path] = data initial[name.String()] = d
} consistent[path] = d
} }
return &MemoryStore{ return &MemoryStore{
data: initial, data: initial,
consistent: consistent, consistent: consistent,
@ -75,6 +79,15 @@ func (m MemoryStore) Get(name string) ([]byte, error) {
func (m *MemoryStore) Set(name string, meta []byte) error { func (m *MemoryStore) Set(name string, meta []byte) error {
m.data[name] = meta m.data[name] = meta
parsedMeta := &data.SignedMeta{}
err := json.Unmarshal(meta, parsedMeta)
if err == nil {
// no parse error means this is metadata and not a key, so store by version
version := parsedMeta.Signed.Version
versionedName := fmt.Sprintf("%d.%s", version, name)
m.data[versionedName] = meta
}
checksum := sha256.Sum256(meta) checksum := sha256.Sum256(meta)
path := utils.ConsistentName(name, checksum[:]) path := utils.ConsistentName(name, checksum[:])
m.consistent[path] = meta m.consistent[path] = meta

View File

@ -1,5 +1,9 @@
package storage package storage
import (
"github.com/docker/notary/tuf/data"
)
// ErrOffline is used to indicate we are operating offline // ErrOffline is used to indicate we are operating offline
type ErrOffline struct{} type ErrOffline struct{}
@ -34,12 +38,12 @@ func (es OfflineStore) Remove(name string) error {
} }
// GetKey returns ErrOffline // GetKey returns ErrOffline
func (es OfflineStore) GetKey(role string) ([]byte, error) { func (es OfflineStore) GetKey(role data.RoleName) ([]byte, error) {
return nil, err return nil, err
} }
// RotateKey returns ErrOffline // RotateKey returns ErrOffline
func (es OfflineStore) RotateKey(role string) ([]byte, error) { func (es OfflineStore) RotateKey(role data.RoleName) ([]byte, error) {
return nil, err return nil, err
} }

31
vendor/github.com/docker/notary/trustmanager/errors.go generated vendored Normal file
View File

@ -0,0 +1,31 @@
package trustmanager
import "fmt"
// ErrAttemptsExceeded is returned when too many attempts have been made to decrypt a key
type ErrAttemptsExceeded struct{}
// ErrAttemptsExceeded is returned when too many attempts have been made to decrypt a key
func (err ErrAttemptsExceeded) Error() string {
return "maximum number of passphrase attempts exceeded"
}
// ErrPasswordInvalid is returned when signing fails. It could also mean the signing
// key file was corrupted, but we have no way to distinguish.
type ErrPasswordInvalid struct{}
// ErrPasswordInvalid is returned when signing fails. It could also mean the signing
// key file was corrupted, but we have no way to distinguish.
func (err ErrPasswordInvalid) Error() string {
return "password invalid, operation has failed."
}
// ErrKeyNotFound is returned when the keystore fails to retrieve a specific key.
type ErrKeyNotFound struct {
KeyID string
}
// ErrKeyNotFound is returned when the keystore fails to retrieve a specific key.
func (err ErrKeyNotFound) Error() string {
return fmt.Sprintf("signing key not found: %s", err.KeyID)
}

View File

@ -1,8 +1,6 @@
package trustmanager package trustmanager
import ( import (
"fmt"
"github.com/docker/notary/tuf/data" "github.com/docker/notary/tuf/data"
) )
@ -34,32 +32,11 @@ type Storage interface {
Location() string Location() string
} }
// ErrAttemptsExceeded is returned when too many attempts have been made to decrypt a key // KeyInfo stores the role and gun for a corresponding private key ID
type ErrAttemptsExceeded struct{} // It is assumed that each private key ID is unique
type KeyInfo struct {
// ErrAttemptsExceeded is returned when too many attempts have been made to decrypt a key Gun data.GUN
func (err ErrAttemptsExceeded) Error() string { Role data.RoleName
return "maximum number of passphrase attempts exceeded"
}
// ErrPasswordInvalid is returned when signing fails. It could also mean the signing
// key file was corrupted, but we have no way to distinguish.
type ErrPasswordInvalid struct{}
// ErrPasswordInvalid is returned when signing fails. It could also mean the signing
// key file was corrupted, but we have no way to distinguish.
func (err ErrPasswordInvalid) Error() string {
return "password invalid, operation has failed."
}
// ErrKeyNotFound is returned when the keystore fails to retrieve a specific key.
type ErrKeyNotFound struct {
KeyID string
}
// ErrKeyNotFound is returned when the keystore fails to retrieve a specific key.
func (err ErrKeyNotFound) Error() string {
return fmt.Sprintf("signing key not found: %s", err.KeyID)
} }
// KeyStore is a generic interface for private key storage // KeyStore is a generic interface for private key storage
@ -69,14 +46,9 @@ type KeyStore interface {
AddKey(keyInfo KeyInfo, privKey data.PrivateKey) error AddKey(keyInfo KeyInfo, privKey data.PrivateKey) error
// Should fail with ErrKeyNotFound if the keystore is operating normally // Should fail with ErrKeyNotFound if the keystore is operating normally
// and knows that it does not store the requested key. // and knows that it does not store the requested key.
GetKey(keyID string) (data.PrivateKey, string, error) GetKey(keyID string) (data.PrivateKey, data.RoleName, error)
GetKeyInfo(keyID string) (KeyInfo, error) GetKeyInfo(keyID string) (KeyInfo, error)
ListKeys() map[string]KeyInfo ListKeys() map[string]KeyInfo
RemoveKey(keyID string) error RemoveKey(keyID string) error
Name() string Name() string
} }
type cachedKey struct {
alias string
key data.PrivateKey
}

View File

@ -1,26 +1,23 @@
package trustmanager package trustmanager
import ( import (
"encoding/pem"
"fmt" "fmt"
"path/filepath" "path/filepath"
"strings" "strings"
"sync" "sync"
"github.com/sirupsen/logrus"
"github.com/docker/notary" "github.com/docker/notary"
store "github.com/docker/notary/storage" store "github.com/docker/notary/storage"
"github.com/docker/notary/tuf/data" "github.com/docker/notary/tuf/data"
"github.com/docker/notary/tuf/utils" "github.com/docker/notary/tuf/utils"
"github.com/sirupsen/logrus"
) )
type keyInfoMap map[string]KeyInfo type keyInfoMap map[string]KeyInfo
// KeyInfo stores the role, path, and gun for a corresponding private key ID type cachedKey struct {
// It is assumed that each private key ID is unique role data.RoleName
type KeyInfo struct { key data.PrivateKey
Gun string
Role string
} }
// GenericKeyStore is a wrapper for Storage instances that provides // GenericKeyStore is a wrapper for Storage instances that provides
@ -80,40 +77,6 @@ func generateKeyInfoMap(s Storage) map[string]KeyInfo {
return keyInfoMap return keyInfoMap
} }
// Attempts to infer the keyID, role, and GUN from the specified key path.
// Note that non-root roles can only be inferred if this is a legacy style filename: KEYID_ROLE.key
func inferKeyInfoFromKeyPath(keyPath string) (string, string, string) {
var keyID, role, gun string
keyID = filepath.Base(keyPath)
underscoreIndex := strings.LastIndex(keyID, "_")
// This is the legacy KEYID_ROLE filename
// The keyID is the first part of the keyname
// The keyRole is the second part of the keyname
// in a key named abcde_root, abcde is the keyID and root is the KeyAlias
if underscoreIndex != -1 {
role = keyID[underscoreIndex+1:]
keyID = keyID[:underscoreIndex]
}
if filepath.HasPrefix(keyPath, notary.RootKeysSubdir+"/") {
return keyID, data.CanonicalRootRole, ""
}
keyPath = strings.TrimPrefix(keyPath, notary.NonRootKeysSubdir+"/")
gun = getGunFromFullID(keyPath)
return keyID, role, gun
}
func getGunFromFullID(fullKeyID string) string {
keyGun := filepath.Dir(fullKeyID)
// If the gun is empty, Dir will return .
if keyGun == "." {
keyGun = ""
}
return keyGun
}
func (s *GenericKeyStore) loadKeyInfo() { func (s *GenericKeyStore) loadKeyInfo() {
s.keyInfoMap = generateKeyInfoMap(s.store) s.keyInfoMap = generateKeyInfoMap(s.store)
} }
@ -139,9 +102,9 @@ func (s *GenericKeyStore) AddKey(keyInfo KeyInfo, privKey data.PrivateKey) error
if keyInfo.Role == data.CanonicalRootRole || data.IsDelegation(keyInfo.Role) || !data.ValidRole(keyInfo.Role) { if keyInfo.Role == data.CanonicalRootRole || data.IsDelegation(keyInfo.Role) || !data.ValidRole(keyInfo.Role) {
keyInfo.Gun = "" keyInfo.Gun = ""
} }
name := filepath.Join(keyInfo.Gun, privKey.ID()) keyID := privKey.ID()
for attempts := 0; ; attempts++ { for attempts := 0; ; attempts++ {
chosenPassphrase, giveup, err = s.PassRetriever(name, keyInfo.Role, true, attempts) chosenPassphrase, giveup, err = s.PassRetriever(keyID, keyInfo.Role.String(), true, attempts)
if err == nil { if err == nil {
break break
} }
@ -150,18 +113,14 @@ func (s *GenericKeyStore) AddKey(keyInfo KeyInfo, privKey data.PrivateKey) error
} }
} }
if chosenPassphrase != "" { pemPrivKey, err = utils.ConvertPrivateKeyToPKCS8(privKey, keyInfo.Role, keyInfo.Gun, chosenPassphrase)
pemPrivKey, err = utils.EncryptPrivateKey(privKey, keyInfo.Role, keyInfo.Gun, chosenPassphrase)
} else {
pemPrivKey, err = utils.KeyToPEM(privKey, keyInfo.Role)
}
if err != nil { if err != nil {
return err return err
} }
s.cachedKeys[name] = &cachedKey{alias: keyInfo.Role, key: privKey} s.cachedKeys[keyID] = &cachedKey{role: keyInfo.Role, key: privKey}
err = s.store.Set(filepath.Join(getSubdir(keyInfo.Role), name), pemPrivKey) err = s.store.Set(keyID, pemPrivKey)
if err != nil { if err != nil {
return err return err
} }
@ -170,15 +129,21 @@ func (s *GenericKeyStore) AddKey(keyInfo KeyInfo, privKey data.PrivateKey) error
} }
// GetKey returns the PrivateKey given a KeyID // GetKey returns the PrivateKey given a KeyID
func (s *GenericKeyStore) GetKey(name string) (data.PrivateKey, string, error) { func (s *GenericKeyStore) GetKey(keyID string) (data.PrivateKey, data.RoleName, error) {
s.Lock() s.Lock()
defer s.Unlock() defer s.Unlock()
cachedKeyEntry, ok := s.cachedKeys[name]
cachedKeyEntry, ok := s.cachedKeys[keyID]
if ok { if ok {
return cachedKeyEntry.key, cachedKeyEntry.alias, nil return cachedKeyEntry.key, cachedKeyEntry.role, nil
} }
keyBytes, _, keyAlias, err := getKey(s.store, name) role, err := getKeyRole(s.store, keyID)
if err != nil {
return nil, "", err
}
keyBytes, err := s.store.Get(keyID)
if err != nil { if err != nil {
return nil, "", err return nil, "", err
} }
@ -186,13 +151,13 @@ func (s *GenericKeyStore) GetKey(name string) (data.PrivateKey, string, error) {
// See if the key is encrypted. If its encrypted we'll fail to parse the private key // See if the key is encrypted. If its encrypted we'll fail to parse the private key
privKey, err := utils.ParsePEMPrivateKey(keyBytes, "") privKey, err := utils.ParsePEMPrivateKey(keyBytes, "")
if err != nil { if err != nil {
privKey, _, err = GetPasswdDecryptBytes(s.PassRetriever, keyBytes, name, string(keyAlias)) privKey, _, err = GetPasswdDecryptBytes(s.PassRetriever, keyBytes, keyID, string(role))
if err != nil { if err != nil {
return nil, "", err return nil, "", err
} }
} }
s.cachedKeys[name] = &cachedKey{alias: keyAlias, key: privKey} s.cachedKeys[keyID] = &cachedKey{role: role, key: privKey}
return privKey, keyAlias, nil return privKey, role, nil
} }
// ListKeys returns a list of unique PublicKeys present on the KeyFileStore, by returning a copy of the keyInfoMap // ListKeys returns a list of unique PublicKeys present on the KeyFileStore, by returning a copy of the keyInfoMap
@ -204,24 +169,14 @@ func (s *GenericKeyStore) ListKeys() map[string]KeyInfo {
func (s *GenericKeyStore) RemoveKey(keyID string) error { func (s *GenericKeyStore) RemoveKey(keyID string) error {
s.Lock() s.Lock()
defer s.Unlock() defer s.Unlock()
_, filename, _, err := getKey(s.store, keyID)
switch err.(type) {
case ErrKeyNotFound, nil:
break
default:
return err
}
delete(s.cachedKeys, keyID) delete(s.cachedKeys, keyID)
err = s.store.Remove(filename) // removing a file that doesn't exist doesn't fail err := s.store.Remove(keyID)
if err != nil { if err != nil {
return err return err
} }
// Remove this key from our keyInfo map if we removed from our filesystem delete(s.keyInfoMap, keyID)
delete(s.keyInfoMap, filepath.Base(keyID))
return nil return nil
} }
@ -242,55 +197,37 @@ func copyKeyInfoMap(keyInfoMap map[string]KeyInfo) map[string]KeyInfo {
// KeyInfoFromPEM attempts to get a keyID and KeyInfo from the filename and PEM bytes of a key // KeyInfoFromPEM attempts to get a keyID and KeyInfo from the filename and PEM bytes of a key
func KeyInfoFromPEM(pemBytes []byte, filename string) (string, KeyInfo, error) { func KeyInfoFromPEM(pemBytes []byte, filename string) (string, KeyInfo, error) {
keyID, role, gun := inferKeyInfoFromKeyPath(filename) var keyID string
if role == "" { keyID = filepath.Base(filename)
block, _ := pem.Decode(pemBytes) role, gun, err := utils.ExtractPrivateKeyAttributes(pemBytes)
if block == nil { if err != nil {
return "", KeyInfo{}, fmt.Errorf("could not decode PEM block for key %s", filename) return "", KeyInfo{}, err
}
if keyRole, ok := block.Headers["role"]; ok {
role = keyRole
}
} }
return keyID, KeyInfo{Gun: gun, Role: role}, nil return keyID, KeyInfo{Gun: gun, Role: role}, nil
} }
// getKey finds the key and role for the given keyID. It attempts to // getKeyRole finds the role for the given keyID. It attempts to look
// look both in the newer format PEM headers, and also in the legacy filename // both in the newer format PEM headers, and also in the legacy filename
// format. It returns: the key bytes, the filename it was found under, the role, // format. It returns: the role, and an error
// and an error func getKeyRole(s Storage, keyID string) (data.RoleName, error) {
func getKey(s Storage, keyID string) ([]byte, string, string, error) {
name := strings.TrimSpace(strings.TrimSuffix(filepath.Base(keyID), filepath.Ext(keyID))) name := strings.TrimSpace(strings.TrimSuffix(filepath.Base(keyID), filepath.Ext(keyID)))
for _, file := range s.ListFiles() { for _, file := range s.ListFiles() {
filename := filepath.Base(file) filename := filepath.Base(file)
if strings.HasPrefix(filename, name) { if strings.HasPrefix(filename, name) {
d, err := s.Get(file) d, err := s.Get(file)
if err != nil { if err != nil {
return nil, "", "", err return "", err
}
block, _ := pem.Decode(d)
if block != nil {
if role, ok := block.Headers["role"]; ok {
return d, file, role, nil
}
} }
role := strings.TrimPrefix(filename, name+"_") role, _, err := utils.ExtractPrivateKeyAttributes(d)
return d, file, role, nil if err != nil {
return "", err
}
return role, nil
} }
} }
return "", ErrKeyNotFound{KeyID: keyID}
return nil, "", "", ErrKeyNotFound{KeyID: keyID}
}
// Assumes 2 subdirectories, 1 containing root keys and 1 containing TUF keys
func getSubdir(alias string) string {
if alias == data.CanonicalRootRole {
return notary.RootKeysSubdir
}
return notary.NonRootKeysSubdir
} }
// GetPasswdDecryptBytes gets the password to decrypt the given pem bytes. // GetPasswdDecryptBytes gets the password to decrypt the given pem bytes.

View File

@ -5,8 +5,10 @@ package yubikey
import ( import (
"encoding/pem" "encoding/pem"
"errors" "errors"
"github.com/docker/notary" "github.com/docker/notary"
"github.com/docker/notary/trustmanager" "github.com/docker/notary/trustmanager"
"github.com/docker/notary/tuf/data"
"github.com/docker/notary/tuf/utils" "github.com/docker/notary/tuf/utils"
) )
@ -39,7 +41,7 @@ func (s *YubiImport) Set(name string, bytes []byte) error {
} }
ki := trustmanager.KeyInfo{ ki := trustmanager.KeyInfo{
// GUN is ignored by YubiStore // GUN is ignored by YubiStore
Role: role, Role: data.RoleName(role),
} }
privKey, err := utils.ParsePEMPrivateKey(bytes, "") privKey, err := utils.ParsePEMPrivateKey(bytes, "")
if err != nil { if err != nil {
@ -47,7 +49,7 @@ func (s *YubiImport) Set(name string, bytes []byte) error {
s.passRetriever, s.passRetriever,
bytes, bytes,
name, name,
ki.Role, ki.Role.String(),
) )
if err != nil { if err != nil {
return err return err

View File

@ -16,13 +16,13 @@ import (
"os" "os"
"time" "time"
"github.com/sirupsen/logrus"
"github.com/docker/notary" "github.com/docker/notary"
"github.com/docker/notary/trustmanager" "github.com/docker/notary/trustmanager"
"github.com/docker/notary/tuf/data" "github.com/docker/notary/tuf/data"
"github.com/docker/notary/tuf/signed" "github.com/docker/notary/tuf/signed"
"github.com/docker/notary/tuf/utils" "github.com/docker/notary/tuf/utils"
"github.com/miekg/pkcs11" "github.com/miekg/pkcs11"
"github.com/sirupsen/logrus"
) )
const ( const (
@ -126,7 +126,7 @@ func (err errHSMNotPresent) Error() string {
} }
type yubiSlot struct { type yubiSlot struct {
role string role data.RoleName
slotID []byte slotID []byte
} }
@ -208,7 +208,7 @@ func (y *YubiPrivateKey) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts
return sig, nil return sig, nil
} }
} }
return nil, errors.New("Failed to generate signature on Yubikey.") return nil, errors.New("failed to generate signature on Yubikey")
} }
// If a byte array is less than the number of bytes specified by // If a byte array is less than the number of bytes specified by
@ -230,7 +230,7 @@ func addECDSAKey(
privKey data.PrivateKey, privKey data.PrivateKey,
pkcs11KeyID []byte, pkcs11KeyID []byte,
passRetriever notary.PassRetriever, passRetriever notary.PassRetriever,
role string, role data.RoleName,
) error { ) error {
logrus.Debugf("Attempting to add key to yubikey with ID: %s", privKey.ID()) logrus.Debugf("Attempting to add key to yubikey with ID: %s", privKey.ID())
@ -250,7 +250,7 @@ func addECDSAKey(
// Hard-coded policy: the generated certificate expires in 10 years. // Hard-coded policy: the generated certificate expires in 10 years.
startTime := time.Now() startTime := time.Now()
template, err := utils.NewCertificate(role, startTime, startTime.AddDate(10, 0, 0)) template, err := utils.NewCertificate(role.String(), startTime, startTime.AddDate(10, 0, 0))
if err != nil { if err != nil {
return fmt.Errorf("failed to create the certificate template: %v", err) return fmt.Errorf("failed to create the certificate template: %v", err)
} }
@ -288,7 +288,7 @@ func addECDSAKey(
return nil return nil
} }
func getECDSAKey(ctx IPKCS11Ctx, session pkcs11.SessionHandle, pkcs11KeyID []byte) (*data.ECDSAPublicKey, string, error) { func getECDSAKey(ctx IPKCS11Ctx, session pkcs11.SessionHandle, pkcs11KeyID []byte) (*data.ECDSAPublicKey, data.RoleName, error) {
findTemplate := []*pkcs11.Attribute{ findTemplate := []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true), pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true),
pkcs11.NewAttribute(pkcs11.CKA_ID, pkcs11KeyID), pkcs11.NewAttribute(pkcs11.CKA_ID, pkcs11KeyID),
@ -448,45 +448,19 @@ func yubiRemoveKey(ctx IPKCS11Ctx, session pkcs11.SessionHandle, pkcs11KeyID []b
func yubiListKeys(ctx IPKCS11Ctx, session pkcs11.SessionHandle) (keys map[string]yubiSlot, err error) { func yubiListKeys(ctx IPKCS11Ctx, session pkcs11.SessionHandle) (keys map[string]yubiSlot, err error) {
keys = make(map[string]yubiSlot) keys = make(map[string]yubiSlot)
findTemplate := []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true),
//pkcs11.NewAttribute(pkcs11.CKA_ID, pkcs11KeyID),
pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_CERTIFICATE),
}
attrTemplate := []*pkcs11.Attribute{ attrTemplate := []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_ID, []byte{0}), pkcs11.NewAttribute(pkcs11.CKA_ID, []byte{0}),
pkcs11.NewAttribute(pkcs11.CKA_VALUE, []byte{0}), pkcs11.NewAttribute(pkcs11.CKA_VALUE, []byte{0}),
} }
if err = ctx.FindObjectsInit(session, findTemplate); err != nil { objs, err := listObjects(ctx, session)
logrus.Debugf("Failed to init: %s", err.Error())
return
}
objs, b, err := ctx.FindObjects(session, numSlots)
for err == nil {
var o []pkcs11.ObjectHandle
o, b, err = ctx.FindObjects(session, numSlots)
if err != nil {
continue
}
if len(o) == 0 {
break
}
objs = append(objs, o...)
}
if err != nil { if err != nil {
logrus.Debugf("Failed to find: %s %v", err.Error(), b) return nil, err
if len(objs) == 0 {
return nil, err
}
}
if err = ctx.FindObjectsFinal(session); err != nil {
logrus.Debugf("Failed to finalize: %s", err.Error())
return
} }
if len(objs) == 0 { if len(objs) == 0 {
return nil, errors.New("No keys found in yubikey.") return nil, errors.New("no keys found in yubikey")
} }
logrus.Debugf("Found %d objects matching list filters", len(objs)) logrus.Debugf("Found %d objects matching list filters", len(objs))
for _, obj := range objs { for _, obj := range objs {
@ -511,7 +485,7 @@ func yubiListKeys(ctx IPKCS11Ctx, session pkcs11.SessionHandle) (keys map[string
if err != nil { if err != nil {
continue continue
} }
if !data.ValidRole(cert.Subject.CommonName) { if !data.ValidRole(data.RoleName(cert.Subject.CommonName)) {
continue continue
} }
} }
@ -538,13 +512,49 @@ func yubiListKeys(ctx IPKCS11Ctx, session pkcs11.SessionHandle) (keys map[string
} }
keys[data.NewECDSAPublicKey(pubBytes).ID()] = yubiSlot{ keys[data.NewECDSAPublicKey(pubBytes).ID()] = yubiSlot{
role: cert.Subject.CommonName, role: data.RoleName(cert.Subject.CommonName),
slotID: slot, slotID: slot,
} }
} }
return return
} }
func listObjects(ctx IPKCS11Ctx, session pkcs11.SessionHandle) ([]pkcs11.ObjectHandle, error) {
findTemplate := []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true),
pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_CERTIFICATE),
}
if err := ctx.FindObjectsInit(session, findTemplate); err != nil {
logrus.Debugf("Failed to init: %s", err.Error())
return nil, err
}
objs, b, err := ctx.FindObjects(session, numSlots)
for err == nil {
var o []pkcs11.ObjectHandle
o, b, err = ctx.FindObjects(session, numSlots)
if err != nil {
continue
}
if len(o) == 0 {
break
}
objs = append(objs, o...)
}
if err != nil {
logrus.Debugf("Failed to find: %s %v", err.Error(), b)
if len(objs) == 0 {
return nil, err
}
}
if err := ctx.FindObjectsFinal(session); err != nil {
logrus.Debugf("Failed to finalize: %s", err.Error())
return nil, err
}
return objs, nil
}
func getNextEmptySlot(ctx IPKCS11Ctx, session pkcs11.SessionHandle) ([]byte, error) { func getNextEmptySlot(ctx IPKCS11Ctx, session pkcs11.SessionHandle) ([]byte, error) {
findTemplate := []*pkcs11.Attribute{ findTemplate := []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true), pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true),
@ -611,7 +621,7 @@ func getNextEmptySlot(ctx IPKCS11Ctx, session pkcs11.SessionHandle) ([]byte, err
return []byte{byte(loc)}, nil return []byte{byte(loc)}, nil
} }
} }
return nil, errors.New("Yubikey has no available slots.") return nil, errors.New("yubikey has no available slots")
} }
// YubiStore is a KeyStore for private keys inside a Yubikey // YubiStore is a KeyStore for private keys inside a Yubikey
@ -687,7 +697,7 @@ func (s *YubiStore) AddKey(keyInfo trustmanager.KeyInfo, privKey data.PrivateKey
// Only add if we haven't seen the key already. Return whether the key was // Only add if we haven't seen the key already. Return whether the key was
// added. // added.
func (s *YubiStore) addKey(keyID, role string, privKey data.PrivateKey) ( func (s *YubiStore) addKey(keyID string, role data.RoleName, privKey data.PrivateKey) (
bool, error) { bool, error) {
// We only allow adding root keys for now // We only allow adding root keys for now
@ -733,7 +743,7 @@ func (s *YubiStore) addKey(keyID, role string, privKey data.PrivateKey) (
// GetKey retrieves a key from the Yubikey only (it does not look inside the // GetKey retrieves a key from the Yubikey only (it does not look inside the
// backup store) // backup store)
func (s *YubiStore) GetKey(keyID string) (data.PrivateKey, string, error) { func (s *YubiStore) GetKey(keyID string) (data.PrivateKey, data.RoleName, error) {
ctx, session, err := SetupHSMEnv(pkcs11Lib, s.libLoader) ctx, session, err := SetupHSMEnv(pkcs11Lib, s.libLoader)
if err != nil { if err != nil {
logrus.Debugf("No yubikey found, using alternative key storage: %s", err.Error()) logrus.Debugf("No yubikey found, using alternative key storage: %s", err.Error())

View File

@ -6,12 +6,14 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/sirupsen/logrus"
"github.com/docker/notary/tuf/data" "github.com/docker/notary/tuf/data"
"github.com/docker/notary/tuf/signed" "github.com/docker/notary/tuf/signed"
"github.com/docker/notary/tuf/utils" "github.com/docker/notary/tuf/utils"
"github.com/sirupsen/logrus"
) )
const wildcard = "*"
// ErrValidationFail is returned when there is no valid trusted certificates // ErrValidationFail is returned when there is no valid trusted certificates
// being served inside of the roots.json // being served inside of the roots.json
type ErrValidationFail struct { type ErrValidationFail struct {
@ -82,7 +84,7 @@ We shall call this: TOFUS.
Validation failure at any step will result in an ErrValidationFailed error. Validation failure at any step will result in an ErrValidationFailed error.
*/ */
func ValidateRoot(prevRoot *data.SignedRoot, root *data.Signed, gun string, trustPinning TrustPinConfig) (*data.SignedRoot, error) { func ValidateRoot(prevRoot *data.SignedRoot, root *data.Signed, gun data.GUN, trustPinning TrustPinConfig) (*data.SignedRoot, error) {
logrus.Debugf("entered ValidateRoot with dns: %s", gun) logrus.Debugf("entered ValidateRoot with dns: %s", gun)
signedRoot, err := data.RootFromSigned(root) signedRoot, err := data.RootFromSigned(root)
if err != nil { if err != nil {
@ -140,7 +142,7 @@ func ValidateRoot(prevRoot *data.SignedRoot, root *data.Signed, gun string, trus
} }
// Regardless of having a previous root or not, confirm that the new root validates against the trust pinning // Regardless of having a previous root or not, confirm that the new root validates against the trust pinning
logrus.Debugf("checking root against trust_pinning config", gun) logrus.Debugf("checking root against trust_pinning config for %s", gun)
trustPinCheckFunc, err := NewTrustPinChecker(trustPinning, gun, !havePrevRoot) trustPinCheckFunc, err := NewTrustPinChecker(trustPinning, gun, !havePrevRoot)
if err != nil { if err != nil {
return nil, &ErrValidationFail{Reason: err.Error()} return nil, &ErrValidationFail{Reason: err.Error()}
@ -175,16 +177,27 @@ func ValidateRoot(prevRoot *data.SignedRoot, root *data.Signed, gun string, trus
return data.RootFromSigned(root) return data.RootFromSigned(root)
} }
// MatchCNToGun checks that the common name in a cert is valid for the given gun.
// This allows wildcards as suffixes, e.g. `namespace/*`
func MatchCNToGun(commonName string, gun data.GUN) bool {
if strings.HasSuffix(commonName, wildcard) {
prefix := strings.TrimRight(commonName, wildcard)
logrus.Debugf("checking gun %s against wildcard prefix %s", gun, prefix)
return strings.HasPrefix(gun.String(), prefix)
}
return commonName == gun.String()
}
// validRootLeafCerts returns a list of possibly (if checkExpiry is true) non-expired, non-sha1 certificates // validRootLeafCerts returns a list of possibly (if checkExpiry is true) non-expired, non-sha1 certificates
// found in root whose Common-Names match the provided GUN. Note that this // found in root whose Common-Names match the provided GUN. Note that this
// "validity" alone does not imply any measure of trust. // "validity" alone does not imply any measure of trust.
func validRootLeafCerts(allLeafCerts map[string]*x509.Certificate, gun string, checkExpiry bool) (map[string]*x509.Certificate, error) { func validRootLeafCerts(allLeafCerts map[string]*x509.Certificate, gun data.GUN, checkExpiry bool) (map[string]*x509.Certificate, error) {
validLeafCerts := make(map[string]*x509.Certificate) validLeafCerts := make(map[string]*x509.Certificate)
// Go through every leaf certificate and check that the CN matches the gun // Go through every leaf certificate and check that the CN matches the gun
for id, cert := range allLeafCerts { for id, cert := range allLeafCerts {
// Validate that this leaf certificate has a CN that matches the exact gun // Validate that this leaf certificate has a CN that matches the gun
if cert.Subject.CommonName != gun { if !MatchCNToGun(cert.Subject.CommonName, gun) {
logrus.Debugf("error leaf certificate CN: %s doesn't match the given GUN: %s", logrus.Debugf("error leaf certificate CN: %s doesn't match the given GUN: %s",
cert.Subject.CommonName, gun) cert.Subject.CommonName, gun)
continue continue

View File

@ -3,9 +3,11 @@ package trustpinning
import ( import (
"crypto/x509" "crypto/x509"
"fmt" "fmt"
"github.com/sirupsen/logrus"
"github.com/docker/notary/tuf/utils"
"strings" "strings"
"github.com/docker/notary/tuf/data"
"github.com/docker/notary/tuf/utils"
"github.com/sirupsen/logrus"
) )
// TrustPinConfig represents the configuration under the trust_pinning section of the config file // TrustPinConfig represents the configuration under the trust_pinning section of the config file
@ -17,7 +19,7 @@ type TrustPinConfig struct {
} }
type trustPinChecker struct { type trustPinChecker struct {
gun string gun data.GUN
config TrustPinConfig config TrustPinConfig
pinnedCAPool *x509.CertPool pinnedCAPool *x509.CertPool
pinnedCertIDs []string pinnedCertIDs []string
@ -27,14 +29,19 @@ type trustPinChecker struct {
type CertChecker func(leafCert *x509.Certificate, intCerts []*x509.Certificate) bool type CertChecker func(leafCert *x509.Certificate, intCerts []*x509.Certificate) bool
// NewTrustPinChecker returns a new certChecker function from a TrustPinConfig for a GUN // NewTrustPinChecker returns a new certChecker function from a TrustPinConfig for a GUN
func NewTrustPinChecker(trustPinConfig TrustPinConfig, gun string, firstBootstrap bool) (CertChecker, error) { func NewTrustPinChecker(trustPinConfig TrustPinConfig, gun data.GUN, firstBootstrap bool) (CertChecker, error) {
t := trustPinChecker{gun: gun, config: trustPinConfig} t := trustPinChecker{gun: gun, config: trustPinConfig}
// Determine the mode, and if it's even valid // Determine the mode, and if it's even valid
if pinnedCerts, ok := trustPinConfig.Certs[gun]; ok { if pinnedCerts, ok := trustPinConfig.Certs[gun.String()]; ok {
logrus.Debugf("trust-pinning using Cert IDs") logrus.Debugf("trust-pinning using Cert IDs")
t.pinnedCertIDs = pinnedCerts t.pinnedCertIDs = pinnedCerts
return t.certsCheck, nil return t.certsCheck, nil
} }
var ok bool
t.pinnedCertIDs, ok = wildcardMatch(gun, trustPinConfig.Certs)
if ok {
return t.certsCheck, nil
}
if caFilepath, err := getPinnedCAFilepathByPrefix(gun, trustPinConfig); err == nil { if caFilepath, err := getPinnedCAFilepathByPrefix(gun, trustPinConfig); err == nil {
logrus.Debugf("trust-pinning using root CA bundle at: %s", caFilepath) logrus.Debugf("trust-pinning using root CA bundle at: %s", caFilepath)
@ -103,19 +110,39 @@ func (t trustPinChecker) tofusCheck(leafCert *x509.Certificate, intCerts []*x509
// Will return the CA filepath corresponding to the most specific (longest) entry in the map that is still a prefix // Will return the CA filepath corresponding to the most specific (longest) entry in the map that is still a prefix
// of the provided gun. Returns an error if no entry matches this GUN as a prefix. // of the provided gun. Returns an error if no entry matches this GUN as a prefix.
func getPinnedCAFilepathByPrefix(gun string, t TrustPinConfig) (string, error) { func getPinnedCAFilepathByPrefix(gun data.GUN, t TrustPinConfig) (string, error) {
specificGUN := "" specificGUN := ""
specificCAFilepath := "" specificCAFilepath := ""
foundCA := false foundCA := false
for gunPrefix, caFilepath := range t.CA { for gunPrefix, caFilepath := range t.CA {
if strings.HasPrefix(gun, gunPrefix) && len(gunPrefix) >= len(specificGUN) { if strings.HasPrefix(gun.String(), gunPrefix) && len(gunPrefix) >= len(specificGUN) {
specificGUN = gunPrefix specificGUN = gunPrefix
specificCAFilepath = caFilepath specificCAFilepath = caFilepath
foundCA = true foundCA = true
} }
} }
if !foundCA { if !foundCA {
return "", fmt.Errorf("could not find pinned CA for GUN: %s\n", gun) return "", fmt.Errorf("could not find pinned CA for GUN: %s", gun)
} }
return specificCAFilepath, nil return specificCAFilepath, nil
} }
// wildcardMatch will attempt to match the most specific (longest prefix) wildcarded
// trustpinning option for key IDs. Given the simple globbing and the use of maps,
// it is impossible to have two different prefixes of equal length.
// This logic also solves the issue of Go's randomization of map iteration.
func wildcardMatch(gun data.GUN, certs map[string][]string) ([]string, bool) {
var (
longest = ""
ids []string
)
for gunPrefix, keyIDs := range certs {
if strings.HasSuffix(gunPrefix, "*") {
if strings.HasPrefix(gun.String(), gunPrefix[:len(gunPrefix)-1]) && len(gunPrefix) > len(longest) {
longest = gunPrefix
ids = keyIDs
}
}
}
return ids, ids != nil
}

View File

@ -28,7 +28,7 @@ func (e ErrInvalidBuilderInput) Error() string {
// ConsistentInfo is the consistent name and size of a role, or just the name // ConsistentInfo is the consistent name and size of a role, or just the name
// of the role and a -1 if no file metadata for the role is known // of the role and a -1 if no file metadata for the role is known
type ConsistentInfo struct { type ConsistentInfo struct {
RoleName string RoleName data.RoleName
fileMeta data.FileMeta fileMeta data.FileMeta
} }
@ -42,7 +42,7 @@ func (c ConsistentInfo) ChecksumKnown() bool {
// ConsistentName returns the consistent name (rolename.sha256) for the role // ConsistentName returns the consistent name (rolename.sha256) for the role
// given this consistent information // given this consistent information
func (c ConsistentInfo) ConsistentName() string { func (c ConsistentInfo) ConsistentName() string {
return utils.ConsistentName(c.RoleName, c.fileMeta.Hashes[notary.SHA256]) return utils.ConsistentName(c.RoleName.String(), c.fileMeta.Hashes[notary.SHA256])
} }
// Length returns the expected length of the role as per this consistent // Length returns the expected length of the role as per this consistent
@ -56,7 +56,8 @@ func (c ConsistentInfo) Length() int64 {
// RepoBuilder is an interface for an object which builds a tuf.Repo // RepoBuilder is an interface for an object which builds a tuf.Repo
type RepoBuilder interface { type RepoBuilder interface {
Load(roleName string, content []byte, minVersion int, allowExpired bool) error Load(roleName data.RoleName, content []byte, minVersion int, allowExpired bool) error
LoadRootForUpdate(content []byte, minVersion int, isFinal bool) error
GenerateSnapshot(prev *data.SignedSnapshot) ([]byte, int, error) GenerateSnapshot(prev *data.SignedSnapshot) ([]byte, int, error)
GenerateTimestamp(prev *data.SignedTimestamp) ([]byte, int, error) GenerateTimestamp(prev *data.SignedTimestamp) ([]byte, int, error)
Finish() (*Repo, *Repo, error) Finish() (*Repo, *Repo, error)
@ -64,15 +65,18 @@ type RepoBuilder interface {
BootstrapNewBuilderWithNewTrustpin(trustpin trustpinning.TrustPinConfig) RepoBuilder BootstrapNewBuilderWithNewTrustpin(trustpin trustpinning.TrustPinConfig) RepoBuilder
// informative functions // informative functions
IsLoaded(roleName string) bool IsLoaded(roleName data.RoleName) bool
GetLoadedVersion(roleName string) int GetLoadedVersion(roleName data.RoleName) int
GetConsistentInfo(roleName string) ConsistentInfo GetConsistentInfo(roleName data.RoleName) ConsistentInfo
} }
// finishedBuilder refuses any more input or output // finishedBuilder refuses any more input or output
type finishedBuilder struct{} type finishedBuilder struct{}
func (f finishedBuilder) Load(roleName string, content []byte, minVersion int, allowExpired bool) error { func (f finishedBuilder) Load(roleName data.RoleName, content []byte, minVersion int, allowExpired bool) error {
return ErrBuildDone
}
func (f finishedBuilder) LoadRootForUpdate(content []byte, minVersion int, isFinal bool) error {
return ErrBuildDone return ErrBuildDone
} }
func (f finishedBuilder) GenerateSnapshot(prev *data.SignedSnapshot) ([]byte, int, error) { func (f finishedBuilder) GenerateSnapshot(prev *data.SignedSnapshot) ([]byte, int, error) {
@ -86,27 +90,27 @@ func (f finishedBuilder) BootstrapNewBuilder() RepoBuilder { return f }
func (f finishedBuilder) BootstrapNewBuilderWithNewTrustpin(trustpin trustpinning.TrustPinConfig) RepoBuilder { func (f finishedBuilder) BootstrapNewBuilderWithNewTrustpin(trustpin trustpinning.TrustPinConfig) RepoBuilder {
return f return f
} }
func (f finishedBuilder) IsLoaded(roleName string) bool { return false } func (f finishedBuilder) IsLoaded(roleName data.RoleName) bool { return false }
func (f finishedBuilder) GetLoadedVersion(roleName string) int { return 0 } func (f finishedBuilder) GetLoadedVersion(roleName data.RoleName) int { return 0 }
func (f finishedBuilder) GetConsistentInfo(roleName string) ConsistentInfo { func (f finishedBuilder) GetConsistentInfo(roleName data.RoleName) ConsistentInfo {
return ConsistentInfo{RoleName: roleName} return ConsistentInfo{RoleName: roleName}
} }
// NewRepoBuilder is the only way to get a pre-built RepoBuilder // NewRepoBuilder is the only way to get a pre-built RepoBuilder
func NewRepoBuilder(gun string, cs signed.CryptoService, trustpin trustpinning.TrustPinConfig) RepoBuilder { func NewRepoBuilder(gun data.GUN, cs signed.CryptoService, trustpin trustpinning.TrustPinConfig) RepoBuilder {
return NewBuilderFromRepo(gun, NewRepo(cs), trustpin) return NewBuilderFromRepo(gun, NewRepo(cs), trustpin)
} }
// NewBuilderFromRepo allows us to bootstrap a builder given existing repo data. // NewBuilderFromRepo allows us to bootstrap a builder given existing repo data.
// YOU PROBABLY SHOULDN'T BE USING THIS OUTSIDE OF TESTING CODE!!! // YOU PROBABLY SHOULDN'T BE USING THIS OUTSIDE OF TESTING CODE!!!
func NewBuilderFromRepo(gun string, repo *Repo, trustpin trustpinning.TrustPinConfig) RepoBuilder { func NewBuilderFromRepo(gun data.GUN, repo *Repo, trustpin trustpinning.TrustPinConfig) RepoBuilder {
return &repoBuilderWrapper{ return &repoBuilderWrapper{
RepoBuilder: &repoBuilder{ RepoBuilder: &repoBuilder{
repo: repo, repo: repo,
invalidRoles: NewRepo(nil), invalidRoles: NewRepo(nil),
gun: gun, gun: gun,
trustpin: trustpin, trustpin: trustpin,
loadedNotChecksummed: make(map[string][]byte), loadedNotChecksummed: make(map[data.RoleName][]byte),
}, },
} }
} }
@ -134,13 +138,13 @@ type repoBuilder struct {
invalidRoles *Repo invalidRoles *Repo
// needed for root trust pininng verification // needed for root trust pininng verification
gun string gun data.GUN
trustpin trustpinning.TrustPinConfig trustpin trustpinning.TrustPinConfig
// in case we load root and/or targets before snapshot and timestamp ( // in case we load root and/or targets before snapshot and timestamp (
// or snapshot and not timestamp), so we know what to verify when the // or snapshot and not timestamp), so we know what to verify when the
// data with checksums come in // data with checksums come in
loadedNotChecksummed map[string][]byte loadedNotChecksummed map[data.RoleName][]byte
// bootstrapped values to validate a new root // bootstrapped values to validate a new root
prevRoot *data.SignedRoot prevRoot *data.SignedRoot
@ -159,7 +163,7 @@ func (rb *repoBuilder) BootstrapNewBuilder() RepoBuilder {
repo: NewRepo(rb.repo.cryptoService), repo: NewRepo(rb.repo.cryptoService),
invalidRoles: NewRepo(nil), invalidRoles: NewRepo(nil),
gun: rb.gun, gun: rb.gun,
loadedNotChecksummed: make(map[string][]byte), loadedNotChecksummed: make(map[data.RoleName][]byte),
trustpin: rb.trustpin, trustpin: rb.trustpin,
prevRoot: rb.repo.Root, prevRoot: rb.repo.Root,
@ -171,7 +175,7 @@ func (rb *repoBuilder) BootstrapNewBuilderWithNewTrustpin(trustpin trustpinning.
return &repoBuilderWrapper{RepoBuilder: &repoBuilder{ return &repoBuilderWrapper{RepoBuilder: &repoBuilder{
repo: NewRepo(rb.repo.cryptoService), repo: NewRepo(rb.repo.cryptoService),
gun: rb.gun, gun: rb.gun,
loadedNotChecksummed: make(map[string][]byte), loadedNotChecksummed: make(map[data.RoleName][]byte),
trustpin: trustpin, trustpin: trustpin,
prevRoot: rb.repo.Root, prevRoot: rb.repo.Root,
@ -180,7 +184,7 @@ func (rb *repoBuilder) BootstrapNewBuilderWithNewTrustpin(trustpin trustpinning.
} }
// IsLoaded returns whether a particular role has already been loaded // IsLoaded returns whether a particular role has already been loaded
func (rb *repoBuilder) IsLoaded(roleName string) bool { func (rb *repoBuilder) IsLoaded(roleName data.RoleName) bool {
switch roleName { switch roleName {
case data.CanonicalRootRole: case data.CanonicalRootRole:
return rb.repo.Root != nil return rb.repo.Root != nil
@ -195,7 +199,7 @@ func (rb *repoBuilder) IsLoaded(roleName string) bool {
// GetLoadedVersion returns the metadata version, if it is loaded, or 1 (the // GetLoadedVersion returns the metadata version, if it is loaded, or 1 (the
// minimum valid version number) otherwise // minimum valid version number) otherwise
func (rb *repoBuilder) GetLoadedVersion(roleName string) int { func (rb *repoBuilder) GetLoadedVersion(roleName data.RoleName) int {
switch { switch {
case roleName == data.CanonicalRootRole && rb.repo.Root != nil: case roleName == data.CanonicalRootRole && rb.repo.Root != nil:
return rb.repo.Root.Signed.Version return rb.repo.Root.Signed.Version
@ -215,7 +219,7 @@ func (rb *repoBuilder) GetLoadedVersion(roleName string) int {
// GetConsistentInfo returns the consistent name and size of a role, if it is known, // GetConsistentInfo returns the consistent name and size of a role, if it is known,
// otherwise just the rolename and a -1 for size (both of which are inside a // otherwise just the rolename and a -1 for size (both of which are inside a
// ConsistentInfo object) // ConsistentInfo object)
func (rb *repoBuilder) GetConsistentInfo(roleName string) ConsistentInfo { func (rb *repoBuilder) GetConsistentInfo(roleName data.RoleName) ConsistentInfo {
info := ConsistentInfo{RoleName: roleName} // starts out with unknown filemeta info := ConsistentInfo{RoleName: roleName} // starts out with unknown filemeta
switch roleName { switch roleName {
case data.CanonicalTimestampRole: case data.CanonicalTimestampRole:
@ -224,29 +228,45 @@ func (rb *repoBuilder) GetConsistentInfo(roleName string) ConsistentInfo {
info.fileMeta.Length = notary.MaxTimestampSize info.fileMeta.Length = notary.MaxTimestampSize
case data.CanonicalSnapshotRole: case data.CanonicalSnapshotRole:
if rb.repo.Timestamp != nil { if rb.repo.Timestamp != nil {
info.fileMeta = rb.repo.Timestamp.Signed.Meta[roleName] info.fileMeta = rb.repo.Timestamp.Signed.Meta[roleName.String()]
} }
case data.CanonicalRootRole: case data.CanonicalRootRole:
switch { switch {
case rb.bootstrappedRootChecksum != nil: case rb.bootstrappedRootChecksum != nil:
info.fileMeta = *rb.bootstrappedRootChecksum info.fileMeta = *rb.bootstrappedRootChecksum
case rb.repo.Snapshot != nil: case rb.repo.Snapshot != nil:
info.fileMeta = rb.repo.Snapshot.Signed.Meta[roleName] info.fileMeta = rb.repo.Snapshot.Signed.Meta[roleName.String()]
} }
default: default:
if rb.repo.Snapshot != nil { if rb.repo.Snapshot != nil {
info.fileMeta = rb.repo.Snapshot.Signed.Meta[roleName] info.fileMeta = rb.repo.Snapshot.Signed.Meta[roleName.String()]
} }
} }
return info return info
} }
func (rb *repoBuilder) Load(roleName string, content []byte, minVersion int, allowExpired bool) error { func (rb *repoBuilder) Load(roleName data.RoleName, content []byte, minVersion int, allowExpired bool) error {
return rb.loadOptions(roleName, content, minVersion, allowExpired, false, false)
}
// LoadRootForUpdate adds additional flags for updating the root.json file
func (rb *repoBuilder) LoadRootForUpdate(content []byte, minVersion int, isFinal bool) error {
if err := rb.loadOptions(data.CanonicalRootRole, content, minVersion, !isFinal, !isFinal, true); err != nil {
return err
}
if !isFinal {
rb.prevRoot = rb.repo.Root
}
return nil
}
// loadOptions adds additional flags that should only be used for updating the root.json
func (rb *repoBuilder) loadOptions(roleName data.RoleName, content []byte, minVersion int, allowExpired, skipChecksum, allowLoaded bool) error {
if !data.ValidRole(roleName) { if !data.ValidRole(roleName) {
return ErrInvalidBuilderInput{msg: fmt.Sprintf("%s is an invalid role", roleName)} return ErrInvalidBuilderInput{msg: fmt.Sprintf("%s is an invalid role", roleName)}
} }
if rb.IsLoaded(roleName) { if !allowLoaded && rb.IsLoaded(roleName) {
return ErrInvalidBuilderInput{msg: fmt.Sprintf("%s has already been loaded", roleName)} return ErrInvalidBuilderInput{msg: fmt.Sprintf("%s has already been loaded", roleName)}
} }
@ -255,9 +275,9 @@ func (rb *repoBuilder) Load(roleName string, content []byte, minVersion int, all
case data.CanonicalRootRole: case data.CanonicalRootRole:
break break
case data.CanonicalTimestampRole, data.CanonicalSnapshotRole, data.CanonicalTargetsRole: case data.CanonicalTimestampRole, data.CanonicalSnapshotRole, data.CanonicalTargetsRole:
err = rb.checkPrereqsLoaded([]string{data.CanonicalRootRole}) err = rb.checkPrereqsLoaded([]data.RoleName{data.CanonicalRootRole})
default: // delegations default: // delegations
err = rb.checkPrereqsLoaded([]string{data.CanonicalRootRole, data.CanonicalTargetsRole}) err = rb.checkPrereqsLoaded([]data.RoleName{data.CanonicalRootRole, data.CanonicalTargetsRole})
} }
if err != nil { if err != nil {
return err return err
@ -265,7 +285,7 @@ func (rb *repoBuilder) Load(roleName string, content []byte, minVersion int, all
switch roleName { switch roleName {
case data.CanonicalRootRole: case data.CanonicalRootRole:
return rb.loadRoot(content, minVersion, allowExpired) return rb.loadRoot(content, minVersion, allowExpired, skipChecksum)
case data.CanonicalSnapshotRole: case data.CanonicalSnapshotRole:
return rb.loadSnapshot(content, minVersion, allowExpired) return rb.loadSnapshot(content, minVersion, allowExpired)
case data.CanonicalTimestampRole: case data.CanonicalTimestampRole:
@ -277,7 +297,7 @@ func (rb *repoBuilder) Load(roleName string, content []byte, minVersion int, all
} }
} }
func (rb *repoBuilder) checkPrereqsLoaded(prereqRoles []string) error { func (rb *repoBuilder) checkPrereqsLoaded(prereqRoles []data.RoleName) error {
for _, req := range prereqRoles { for _, req := range prereqRoles {
if !rb.IsLoaded(req) { if !rb.IsLoaded(req) {
return ErrInvalidBuilderInput{msg: fmt.Sprintf("%s must be loaded first", req)} return ErrInvalidBuilderInput{msg: fmt.Sprintf("%s must be loaded first", req)}
@ -301,7 +321,7 @@ func (rb *repoBuilder) GenerateSnapshot(prev *data.SignedSnapshot) ([]byte, int,
return nil, 0, ErrInvalidBuilderInput{msg: "cannot generate snapshot if timestamp has already been loaded"} return nil, 0, ErrInvalidBuilderInput{msg: "cannot generate snapshot if timestamp has already been loaded"}
} }
if err := rb.checkPrereqsLoaded([]string{data.CanonicalRootRole}); err != nil { if err := rb.checkPrereqsLoaded([]data.RoleName{data.CanonicalRootRole}); err != nil {
return nil, 0, err return nil, 0, err
} }
@ -310,7 +330,7 @@ func (rb *repoBuilder) GenerateSnapshot(prev *data.SignedSnapshot) ([]byte, int,
// valid (it has a targets meta), we're good. // valid (it has a targets meta), we're good.
switch prev { switch prev {
case nil: case nil:
if err := rb.checkPrereqsLoaded([]string{data.CanonicalTargetsRole}); err != nil { if err := rb.checkPrereqsLoaded([]data.RoleName{data.CanonicalTargetsRole}); err != nil {
return nil, 0, err return nil, 0, err
} }
@ -342,7 +362,7 @@ func (rb *repoBuilder) GenerateSnapshot(prev *data.SignedSnapshot) ([]byte, int,
// the root and targets data (there may not be any) that that have been loaded, // the root and targets data (there may not be any) that that have been loaded,
// remove all of them from rb.loadedNotChecksummed // remove all of them from rb.loadedNotChecksummed
for tgtName := range rb.repo.Targets { for tgtName := range rb.repo.Targets {
delete(rb.loadedNotChecksummed, tgtName) delete(rb.loadedNotChecksummed, data.RoleName(tgtName))
} }
delete(rb.loadedNotChecksummed, data.CanonicalRootRole) delete(rb.loadedNotChecksummed, data.CanonicalRootRole)
@ -367,7 +387,7 @@ func (rb *repoBuilder) GenerateTimestamp(prev *data.SignedTimestamp) ([]byte, in
// SignTimestamp always serializes the loaded snapshot and signs in the data, so we must always // SignTimestamp always serializes the loaded snapshot and signs in the data, so we must always
// have the snapshot loaded first // have the snapshot loaded first
if err := rb.checkPrereqsLoaded([]string{data.CanonicalRootRole, data.CanonicalSnapshotRole}); err != nil { if err := rb.checkPrereqsLoaded([]data.RoleName{data.CanonicalRootRole, data.CanonicalSnapshotRole}); err != nil {
return nil, 0, err return nil, 0, err
} }
@ -408,10 +428,10 @@ func (rb *repoBuilder) GenerateTimestamp(prev *data.SignedTimestamp) ([]byte, in
} }
// loadRoot loads a root if one has not been loaded // loadRoot loads a root if one has not been loaded
func (rb *repoBuilder) loadRoot(content []byte, minVersion int, allowExpired bool) error { func (rb *repoBuilder) loadRoot(content []byte, minVersion int, allowExpired, skipChecksum bool) error {
roleName := data.CanonicalRootRole roleName := data.CanonicalRootRole
signedObj, err := rb.bytesToSigned(content, data.CanonicalRootRole) signedObj, err := rb.bytesToSigned(content, data.CanonicalRootRole, skipChecksum)
if err != nil { if err != nil {
return err return err
} }
@ -511,7 +531,7 @@ func (rb *repoBuilder) loadSnapshot(content []byte, minVersion int, allowExpired
// this snapshot to bootstrap the next builder if needed - and we don't need to do // this snapshot to bootstrap the next builder if needed - and we don't need to do
// the 2-value assignment since we've already validated the signedSnapshot, which MUST // the 2-value assignment since we've already validated the signedSnapshot, which MUST
// have root metadata // have root metadata
rootMeta := signedSnapshot.Signed.Meta[data.CanonicalRootRole] rootMeta := signedSnapshot.Signed.Meta[data.CanonicalRootRole.String()]
rb.nextRootChecksum = &rootMeta rb.nextRootChecksum = &rootMeta
if err := rb.validateChecksumsFromSnapshot(signedSnapshot); err != nil { if err := rb.validateChecksumsFromSnapshot(signedSnapshot); err != nil {
@ -555,14 +575,14 @@ func (rb *repoBuilder) loadTargets(content []byte, minVersion int, allowExpired
return nil return nil
} }
func (rb *repoBuilder) loadDelegation(roleName string, content []byte, minVersion int, allowExpired bool) error { func (rb *repoBuilder) loadDelegation(roleName data.RoleName, content []byte, minVersion int, allowExpired bool) error {
delegationRole, err := rb.repo.GetDelegationRole(roleName) delegationRole, err := rb.repo.GetDelegationRole(roleName)
if err != nil { if err != nil {
return err return err
} }
// bytesToSigned checks checksum // bytesToSigned checks checksum
signedObj, err := rb.bytesToSigned(content, roleName) signedObj, err := rb.bytesToSigned(content, roleName, false)
if err != nil { if err != nil {
return err return err
} }
@ -599,8 +619,8 @@ func (rb *repoBuilder) validateChecksumsFromTimestamp(ts *data.SignedTimestamp)
sn, ok := rb.loadedNotChecksummed[data.CanonicalSnapshotRole] sn, ok := rb.loadedNotChecksummed[data.CanonicalSnapshotRole]
if ok { if ok {
// by this point, the SignedTimestamp has been validated so it must have a snapshot hash // by this point, the SignedTimestamp has been validated so it must have a snapshot hash
snMeta := ts.Signed.Meta[data.CanonicalSnapshotRole].Hashes snMeta := ts.Signed.Meta[data.CanonicalSnapshotRole.String()].Hashes
if err := data.CheckHashes(sn, data.CanonicalSnapshotRole, snMeta); err != nil { if err := data.CheckHashes(sn, data.CanonicalSnapshotRole.String(), snMeta); err != nil {
return err return err
} }
delete(rb.loadedNotChecksummed, data.CanonicalSnapshotRole) delete(rb.loadedNotChecksummed, data.CanonicalSnapshotRole)
@ -609,13 +629,13 @@ func (rb *repoBuilder) validateChecksumsFromTimestamp(ts *data.SignedTimestamp)
} }
func (rb *repoBuilder) validateChecksumsFromSnapshot(sn *data.SignedSnapshot) error { func (rb *repoBuilder) validateChecksumsFromSnapshot(sn *data.SignedSnapshot) error {
var goodRoles []string var goodRoles []data.RoleName
for roleName, loadedBytes := range rb.loadedNotChecksummed { for roleName, loadedBytes := range rb.loadedNotChecksummed {
switch roleName { switch roleName {
case data.CanonicalSnapshotRole, data.CanonicalTimestampRole: case data.CanonicalSnapshotRole, data.CanonicalTimestampRole:
break break
default: default:
if err := data.CheckHashes(loadedBytes, roleName, sn.Signed.Meta[roleName].Hashes); err != nil { if err := data.CheckHashes(loadedBytes, roleName.String(), sn.Signed.Meta[roleName.String()].Hashes); err != nil {
return err return err
} }
goodRoles = append(goodRoles, roleName) goodRoles = append(goodRoles, roleName)
@ -627,10 +647,10 @@ func (rb *repoBuilder) validateChecksumsFromSnapshot(sn *data.SignedSnapshot) er
return nil return nil
} }
func (rb *repoBuilder) validateChecksumFor(content []byte, roleName string) error { func (rb *repoBuilder) validateChecksumFor(content []byte, roleName data.RoleName) error {
// validate the bootstrap checksum for root, if provided // validate the bootstrap checksum for root, if provided
if roleName == data.CanonicalRootRole && rb.bootstrappedRootChecksum != nil { if roleName == data.CanonicalRootRole && rb.bootstrappedRootChecksum != nil {
if err := data.CheckHashes(content, roleName, rb.bootstrappedRootChecksum.Hashes); err != nil { if err := data.CheckHashes(content, roleName.String(), rb.bootstrappedRootChecksum.Hashes); err != nil {
return err return err
} }
} }
@ -639,7 +659,7 @@ func (rb *repoBuilder) validateChecksumFor(content []byte, roleName string) erro
// loaded it is validated (to make sure everything in the repo is self-consistent) // loaded it is validated (to make sure everything in the repo is self-consistent)
checksums := rb.getChecksumsFor(roleName) checksums := rb.getChecksumsFor(roleName)
if checksums != nil { // as opposed to empty, in which case hash check should fail if checksums != nil { // as opposed to empty, in which case hash check should fail
if err := data.CheckHashes(content, roleName, *checksums); err != nil { if err := data.CheckHashes(content, roleName.String(), *checksums); err != nil {
return err return err
} }
} else if roleName != data.CanonicalTimestampRole { } else if roleName != data.CanonicalTimestampRole {
@ -655,9 +675,11 @@ func (rb *repoBuilder) validateChecksumFor(content []byte, roleName string) erro
// Checksums the given bytes, and if they validate, convert to a data.Signed object. // Checksums the given bytes, and if they validate, convert to a data.Signed object.
// If a checksums are nil (as opposed to empty), adds the bytes to the list of roles that // If a checksums are nil (as opposed to empty), adds the bytes to the list of roles that
// haven't been checksummed (unless it's a timestamp, which has no checksum reference). // haven't been checksummed (unless it's a timestamp, which has no checksum reference).
func (rb *repoBuilder) bytesToSigned(content []byte, roleName string) (*data.Signed, error) { func (rb *repoBuilder) bytesToSigned(content []byte, roleName data.RoleName, skipChecksum bool) (*data.Signed, error) {
if err := rb.validateChecksumFor(content, roleName); err != nil { if !skipChecksum {
return nil, err if err := rb.validateChecksumFor(content, roleName); err != nil {
return nil, err
}
} }
// unmarshal to signed // unmarshal to signed
@ -671,7 +693,7 @@ func (rb *repoBuilder) bytesToSigned(content []byte, roleName string) (*data.Sig
func (rb *repoBuilder) bytesToSignedAndValidateSigs(role data.BaseRole, content []byte) (*data.Signed, error) { func (rb *repoBuilder) bytesToSignedAndValidateSigs(role data.BaseRole, content []byte) (*data.Signed, error) {
signedObj, err := rb.bytesToSigned(content, role.Name) signedObj, err := rb.bytesToSigned(content, role.Name, false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -690,7 +712,7 @@ func (rb *repoBuilder) bytesToSignedAndValidateSigs(role data.BaseRole, content
// available. If the checksum reference *is* loaded, then always returns the // available. If the checksum reference *is* loaded, then always returns the
// Hashes object for the given role - if it doesn't exist, returns an empty Hash // Hashes object for the given role - if it doesn't exist, returns an empty Hash
// object (against which any checksum validation would fail). // object (against which any checksum validation would fail).
func (rb *repoBuilder) getChecksumsFor(role string) *data.Hashes { func (rb *repoBuilder) getChecksumsFor(role data.RoleName) *data.Hashes {
var hashes data.Hashes var hashes data.Hashes
switch role { switch role {
case data.CanonicalTimestampRole: case data.CanonicalTimestampRole:
@ -699,12 +721,12 @@ func (rb *repoBuilder) getChecksumsFor(role string) *data.Hashes {
if rb.repo.Timestamp == nil { if rb.repo.Timestamp == nil {
return nil return nil
} }
hashes = rb.repo.Timestamp.Signed.Meta[data.CanonicalSnapshotRole].Hashes hashes = rb.repo.Timestamp.Signed.Meta[data.CanonicalSnapshotRole.String()].Hashes
default: default:
if rb.repo.Snapshot == nil { if rb.repo.Snapshot == nil {
return nil return nil
} }
hashes = rb.repo.Snapshot.Signed.Meta[role].Hashes hashes = rb.repo.Snapshot.Signed.Meta[role.String()].Hashes
} }
return &hashes return &hashes
} }

View File

@ -4,12 +4,12 @@ import "fmt"
// ErrInvalidMetadata is the error to be returned when metadata is invalid // ErrInvalidMetadata is the error to be returned when metadata is invalid
type ErrInvalidMetadata struct { type ErrInvalidMetadata struct {
role string role RoleName
msg string msg string
} }
func (e ErrInvalidMetadata) Error() string { func (e ErrInvalidMetadata) Error() string {
return fmt.Sprintf("%s type metadata invalid: %s", e.role, e.msg) return fmt.Sprintf("%s type metadata invalid: %s", e.role.String(), e.msg)
} }
// ErrMissingMeta - couldn't find the FileMeta object for the given Role, or // ErrMissingMeta - couldn't find the FileMeta object for the given Role, or

View File

@ -12,9 +12,9 @@ import (
"io" "io"
"math/big" "math/big"
"github.com/sirupsen/logrus"
"github.com/agl/ed25519" "github.com/agl/ed25519"
"github.com/docker/go/canonical/json" "github.com/docker/go/canonical/json"
"github.com/sirupsen/logrus"
) )
// PublicKey is the necessary interface for public keys // PublicKey is the necessary interface for public keys
@ -376,7 +376,7 @@ func NewECDSAPrivateKey(public PublicKey, private []byte) (*ECDSAPrivateKey, err
switch public.(type) { switch public.(type) {
case *ECDSAPublicKey, *ECDSAx509PublicKey: case *ECDSAPublicKey, *ECDSAx509PublicKey:
default: default:
return nil, errors.New("Invalid public key type provided to NewECDSAPrivateKey") return nil, errors.New("invalid public key type provided to NewECDSAPrivateKey")
} }
ecdsaPrivKey, err := x509.ParseECPrivateKey(private) ecdsaPrivKey, err := x509.ParseECPrivateKey(private)
if err != nil { if err != nil {
@ -394,7 +394,7 @@ func NewRSAPrivateKey(public PublicKey, private []byte) (*RSAPrivateKey, error)
switch public.(type) { switch public.(type) {
case *RSAPublicKey, *RSAx509PublicKey: case *RSAPublicKey, *RSAx509PublicKey:
default: default:
return nil, errors.New("Invalid public key type provided to NewRSAPrivateKey") return nil, errors.New("invalid public key type provided to NewRSAPrivateKey")
} }
rsaPrivKey, err := x509.ParsePKCS1PrivateKey(private) rsaPrivKey, err := x509.ParsePKCS1PrivateKey(private)
if err != nil { if err != nil {
@ -445,7 +445,7 @@ type ecdsaSig struct {
func (k ECDSAPrivateKey) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts) (signature []byte, err error) { func (k ECDSAPrivateKey) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts) (signature []byte, err error) {
ecdsaPrivKey, ok := k.CryptoSigner().(*ecdsa.PrivateKey) ecdsaPrivKey, ok := k.CryptoSigner().(*ecdsa.PrivateKey)
if !ok { if !ok {
return nil, errors.New("Signer was based on the wrong key type") return nil, errors.New("signer was based on the wrong key type")
} }
hashed := sha256.Sum256(msg) hashed := sha256.Sum256(msg)
sigASN1, err := ecdsaPrivKey.Sign(rand, hashed[:], opts) sigASN1, err := ecdsaPrivKey.Sign(rand, hashed[:], opts)
@ -492,7 +492,7 @@ func (k ED25519PrivateKey) Sign(rand io.Reader, msg []byte, opts crypto.SignerOp
// Sign on an UnknownPrivateKey raises an error because the client does not // Sign on an UnknownPrivateKey raises an error because the client does not
// know how to sign with this key type. // know how to sign with this key type.
func (k UnknownPrivateKey) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts) (signature []byte, err error) { func (k UnknownPrivateKey) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts) (signature []byte, err error) {
return nil, errors.New("Unknown key type, cannot sign.") return nil, errors.New("unknown key type, cannot sign")
} }
// SignatureAlgorithm returns the SigAlgorithm for a ECDSAPrivateKey // SignatureAlgorithm returns the SigAlgorithm for a ECDSAPrivateKey

View File

@ -10,16 +10,16 @@ import (
) )
// Canonical base role names // Canonical base role names
const ( var (
CanonicalRootRole = "root" CanonicalRootRole RoleName = "root"
CanonicalTargetsRole = "targets" CanonicalTargetsRole RoleName = "targets"
CanonicalSnapshotRole = "snapshot" CanonicalSnapshotRole RoleName = "snapshot"
CanonicalTimestampRole = "timestamp" CanonicalTimestampRole RoleName = "timestamp"
) )
// BaseRoles is an easy to iterate list of the top level // BaseRoles is an easy to iterate list of the top level
// roles. // roles.
var BaseRoles = []string{ var BaseRoles = []RoleName{
CanonicalRootRole, CanonicalRootRole,
CanonicalTargetsRole, CanonicalTargetsRole,
CanonicalSnapshotRole, CanonicalSnapshotRole,
@ -31,7 +31,7 @@ var delegationRegexp = regexp.MustCompile("^[-a-z0-9_/]+$")
// ErrNoSuchRole indicates the roles doesn't exist // ErrNoSuchRole indicates the roles doesn't exist
type ErrNoSuchRole struct { type ErrNoSuchRole struct {
Role string Role RoleName
} }
func (e ErrNoSuchRole) Error() string { func (e ErrNoSuchRole) Error() string {
@ -42,7 +42,7 @@ func (e ErrNoSuchRole) Error() string {
// something like a role for which sone of the public keys were // something like a role for which sone of the public keys were
// not found in the TUF repo. // not found in the TUF repo.
type ErrInvalidRole struct { type ErrInvalidRole struct {
Role string Role RoleName
Reason string Reason string
} }
@ -56,7 +56,7 @@ func (e ErrInvalidRole) Error() string {
// ValidRole only determines the name is semantically // ValidRole only determines the name is semantically
// correct. For target delegated roles, it does NOT check // correct. For target delegated roles, it does NOT check
// the the appropriate parent roles exist. // the the appropriate parent roles exist.
func ValidRole(name string) bool { func ValidRole(name RoleName) bool {
if IsDelegation(name) { if IsDelegation(name) {
return true return true
} }
@ -70,24 +70,25 @@ func ValidRole(name string) bool {
} }
// IsDelegation checks if the role is a delegation or a root role // IsDelegation checks if the role is a delegation or a root role
func IsDelegation(role string) bool { func IsDelegation(role RoleName) bool {
strRole := role.String()
targetsBase := CanonicalTargetsRole + "/" targetsBase := CanonicalTargetsRole + "/"
whitelistedChars := delegationRegexp.MatchString(role) whitelistedChars := delegationRegexp.MatchString(strRole)
// Limit size of full role string to 255 chars for db column size limit // Limit size of full role string to 255 chars for db column size limit
correctLength := len(role) < 256 correctLength := len(role) < 256
// Removes ., .., extra slashes, and trailing slash // Removes ., .., extra slashes, and trailing slash
isClean := path.Clean(role) == role isClean := path.Clean(strRole) == strRole
return strings.HasPrefix(role, targetsBase) && return strings.HasPrefix(strRole, targetsBase.String()) &&
whitelistedChars && whitelistedChars &&
correctLength && correctLength &&
isClean isClean
} }
// IsBaseRole checks if the role is a base role // IsBaseRole checks if the role is a base role
func IsBaseRole(role string) bool { func IsBaseRole(role RoleName) bool {
for _, baseRole := range BaseRoles { for _, baseRole := range BaseRoles {
if role == baseRole { if role == baseRole {
return true return true
@ -100,11 +101,11 @@ func IsBaseRole(role string) bool {
// path, i.e. targets/*, targets/foo/*. // path, i.e. targets/*, targets/foo/*.
// The wildcard may only appear as the final part of the delegation and must // The wildcard may only appear as the final part of the delegation and must
// be a whole segment, i.e. targets/foo* is not a valid wildcard delegation. // be a whole segment, i.e. targets/foo* is not a valid wildcard delegation.
func IsWildDelegation(role string) bool { func IsWildDelegation(role RoleName) bool {
if path.Clean(role) != role { if path.Clean(role.String()) != role.String() {
return false return false
} }
base := path.Dir(role) base := role.Parent()
if !(IsDelegation(base) || base == CanonicalTargetsRole) { if !(IsDelegation(base) || base == CanonicalTargetsRole) {
return false return false
} }
@ -114,12 +115,12 @@ func IsWildDelegation(role string) bool {
// BaseRole is an internal representation of a root/targets/snapshot/timestamp role, with its public keys included // BaseRole is an internal representation of a root/targets/snapshot/timestamp role, with its public keys included
type BaseRole struct { type BaseRole struct {
Keys map[string]PublicKey Keys map[string]PublicKey
Name string Name RoleName
Threshold int Threshold int
} }
// NewBaseRole creates a new BaseRole object with the provided parameters // NewBaseRole creates a new BaseRole object with the provided parameters
func NewBaseRole(name string, threshold int, keys ...PublicKey) BaseRole { func NewBaseRole(name RoleName, threshold int, keys ...PublicKey) BaseRole {
r := BaseRole{ r := BaseRole{
Name: name, Name: name,
Threshold: threshold, Threshold: threshold,
@ -199,7 +200,7 @@ func (d DelegationRole) Restrict(child DelegationRole) (DelegationRole, error) {
// determined by delegation name. // determined by delegation name.
// Ex: targets/a is a direct parent of targets/a/b, but targets/a is not a direct parent of targets/a/b/c // Ex: targets/a is a direct parent of targets/a/b, but targets/a is not a direct parent of targets/a/b/c
func (d DelegationRole) IsParentOf(child DelegationRole) bool { func (d DelegationRole) IsParentOf(child DelegationRole) bool {
return path.Dir(child.Name) == d.Name return path.Dir(child.Name.String()) == d.Name.String()
} }
// CheckPaths checks if a given path is valid for the role // CheckPaths checks if a given path is valid for the role
@ -251,12 +252,12 @@ type RootRole struct {
// Eventually should only be used for immediately before and after serialization/deserialization // Eventually should only be used for immediately before and after serialization/deserialization
type Role struct { type Role struct {
RootRole RootRole
Name string `json:"name"` Name RoleName `json:"name"`
Paths []string `json:"paths,omitempty"` Paths []string `json:"paths,omitempty"`
} }
// NewRole creates a new Role object from the given parameters // NewRole creates a new Role object from the given parameters
func NewRole(name string, threshold int, keyIDs, paths []string) (*Role, error) { func NewRole(name RoleName, threshold int, keyIDs, paths []string) (*Role, error) {
if IsDelegation(name) { if IsDelegation(name) {
if len(paths) == 0 { if len(paths) == 0 {
logrus.Debugf("role %s with no Paths will never be able to publish content until one or more are added", name) logrus.Debugf("role %s with no Paths will never be able to publish content until one or more are added", name)

View File

@ -16,9 +16,9 @@ type SignedRoot struct {
// Root is the Signed component of a root.json // Root is the Signed component of a root.json
type Root struct { type Root struct {
SignedCommon SignedCommon
Keys Keys `json:"keys"` Keys Keys `json:"keys"`
Roles map[string]*RootRole `json:"roles"` Roles map[RoleName]*RootRole `json:"roles"`
ConsistentSnapshot bool `json:"consistent_snapshot"` ConsistentSnapshot bool `json:"consistent_snapshot"`
} }
// isValidRootStructure returns an error, or nil, depending on whether the content of the struct // isValidRootStructure returns an error, or nil, depending on whether the content of the struct
@ -51,7 +51,7 @@ func isValidRootStructure(r Root) error {
return nil return nil
} }
func isValidRootRoleStructure(metaContainingRole, rootRoleName string, r RootRole, validKeys Keys) error { func isValidRootRoleStructure(metaContainingRole, rootRoleName RoleName, r RootRole, validKeys Keys) error {
if r.Threshold < 1 { if r.Threshold < 1 {
return ErrInvalidMetadata{ return ErrInvalidMetadata{
role: metaContainingRole, role: metaContainingRole,
@ -70,7 +70,7 @@ func isValidRootRoleStructure(metaContainingRole, rootRoleName string, r RootRol
} }
// NewRoot initializes a new SignedRoot with a set of keys, roles, and the consistent flag // NewRoot initializes a new SignedRoot with a set of keys, roles, and the consistent flag
func NewRoot(keys map[string]PublicKey, roles map[string]*RootRole, consistent bool) (*SignedRoot, error) { func NewRoot(keys map[string]PublicKey, roles map[RoleName]*RootRole, consistent bool) (*SignedRoot, error) {
signedRoot := &SignedRoot{ signedRoot := &SignedRoot{
Signatures: make([]Signature, 0), Signatures: make([]Signature, 0),
Signed: Root{ Signed: Root{
@ -91,7 +91,7 @@ func NewRoot(keys map[string]PublicKey, roles map[string]*RootRole, consistent b
// BuildBaseRole returns a copy of a BaseRole using the information in this SignedRoot for the specified role name. // BuildBaseRole returns a copy of a BaseRole using the information in this SignedRoot for the specified role name.
// Will error for invalid role name or key metadata within this SignedRoot // Will error for invalid role name or key metadata within this SignedRoot
func (r SignedRoot) BuildBaseRole(roleName string) (BaseRole, error) { func (r SignedRoot) BuildBaseRole(roleName RoleName) (BaseRole, error) {
roleData, ok := r.Signed.Roles[roleName] roleData, ok := r.Signed.Roles[roleName]
if !ok { if !ok {
return BaseRole{}, ErrInvalidRole{Role: roleName, Reason: "role not found in root file"} return BaseRole{}, ErrInvalidRole{Role: roleName, Reason: "role not found in root file"}

View File

@ -4,9 +4,9 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"github.com/sirupsen/logrus"
"github.com/docker/go/canonical/json" "github.com/docker/go/canonical/json"
"github.com/docker/notary" "github.com/docker/notary"
"github.com/sirupsen/logrus"
) )
// SignedSnapshot is a fully unpacked snapshot.json // SignedSnapshot is a fully unpacked snapshot.json
@ -37,22 +37,22 @@ func IsValidSnapshotStructure(s Snapshot) error {
role: CanonicalSnapshotRole, msg: "version cannot be less than one"} role: CanonicalSnapshotRole, msg: "version cannot be less than one"}
} }
for _, role := range []string{CanonicalRootRole, CanonicalTargetsRole} { for _, file := range []RoleName{CanonicalRootRole, CanonicalTargetsRole} {
// Meta is a map of FileMeta, so if the role isn't in the map it returns // Meta is a map of FileMeta, so if the role isn't in the map it returns
// an empty FileMeta, which has an empty map, and you can check on keys // an empty FileMeta, which has an empty map, and you can check on keys
// from an empty map. // from an empty map.
// //
// For now sha256 is required and sha512 is not. // For now sha256 is required and sha512 is not.
if _, ok := s.Meta[role].Hashes[notary.SHA256]; !ok { if _, ok := s.Meta[file.String()].Hashes[notary.SHA256]; !ok {
return ErrInvalidMetadata{ return ErrInvalidMetadata{
role: CanonicalSnapshotRole, role: CanonicalSnapshotRole,
msg: fmt.Sprintf("missing %s sha256 checksum information", role), msg: fmt.Sprintf("missing %s sha256 checksum information", file.String()),
} }
} }
if err := CheckValidHashStructures(s.Meta[role].Hashes); err != nil { if err := CheckValidHashStructures(s.Meta[file.String()].Hashes); err != nil {
return ErrInvalidMetadata{ return ErrInvalidMetadata{
role: CanonicalSnapshotRole, role: CanonicalSnapshotRole,
msg: fmt.Sprintf("invalid %s checksum information, %v", role, err), msg: fmt.Sprintf("invalid %s checksum information, %v", file.String(), err),
} }
} }
} }
@ -90,8 +90,8 @@ func NewSnapshot(root *Signed, targets *Signed) (*SignedSnapshot, error) {
Expires: DefaultExpires(CanonicalSnapshotRole), Expires: DefaultExpires(CanonicalSnapshotRole),
}, },
Meta: Files{ Meta: Files{
CanonicalRootRole: rootMeta, CanonicalRootRole.String(): rootMeta,
CanonicalTargetsRole: targetsMeta, CanonicalTargetsRole.String(): targetsMeta,
}, },
}, },
}, nil }, nil
@ -117,27 +117,27 @@ func (sp *SignedSnapshot) ToSigned() (*Signed, error) {
} }
// AddMeta updates a role in the snapshot with new meta // AddMeta updates a role in the snapshot with new meta
func (sp *SignedSnapshot) AddMeta(role string, meta FileMeta) { func (sp *SignedSnapshot) AddMeta(role RoleName, meta FileMeta) {
sp.Signed.Meta[role] = meta sp.Signed.Meta[role.String()] = meta
sp.Dirty = true sp.Dirty = true
} }
// GetMeta gets the metadata for a particular role, returning an error if it's // GetMeta gets the metadata for a particular role, returning an error if it's
// not found // not found
func (sp *SignedSnapshot) GetMeta(role string) (*FileMeta, error) { func (sp *SignedSnapshot) GetMeta(role RoleName) (*FileMeta, error) {
if meta, ok := sp.Signed.Meta[role]; ok { if meta, ok := sp.Signed.Meta[role.String()]; ok {
if _, ok := meta.Hashes["sha256"]; ok { if _, ok := meta.Hashes["sha256"]; ok {
return &meta, nil return &meta, nil
} }
} }
return nil, ErrMissingMeta{Role: role} return nil, ErrMissingMeta{Role: role.String()}
} }
// DeleteMeta removes a role from the snapshot. If the role doesn't // DeleteMeta removes a role from the snapshot. If the role doesn't
// exist in the snapshot, it's a noop. // exist in the snapshot, it's a noop.
func (sp *SignedSnapshot) DeleteMeta(role string) { func (sp *SignedSnapshot) DeleteMeta(role RoleName) {
if _, ok := sp.Signed.Meta[role]; ok { if _, ok := sp.Signed.Meta[role.String()]; ok {
delete(sp.Signed.Meta, role) delete(sp.Signed.Meta, role.String())
sp.Dirty = true sp.Dirty = true
} }
} }

View File

@ -26,7 +26,7 @@ type Targets struct {
// isValidTargetsStructure returns an error, or nil, depending on whether the content of the struct // isValidTargetsStructure returns an error, or nil, depending on whether the content of the struct
// is valid for targets metadata. This does not check signatures or expiry, just that // is valid for targets metadata. This does not check signatures or expiry, just that
// the metadata content is valid. // the metadata content is valid.
func isValidTargetsStructure(t Targets, roleName string) error { func isValidTargetsStructure(t Targets, roleName RoleName) error {
if roleName != CanonicalTargetsRole && !IsDelegation(roleName) { if roleName != CanonicalTargetsRole && !IsDelegation(roleName) {
return ErrInvalidRole{Role: roleName} return ErrInvalidRole{Role: roleName}
} }
@ -43,7 +43,7 @@ func isValidTargetsStructure(t Targets, roleName string) error {
} }
for _, roleObj := range t.Delegations.Roles { for _, roleObj := range t.Delegations.Roles {
if !IsDelegation(roleObj.Name) || path.Dir(roleObj.Name) != roleName { if !IsDelegation(roleObj.Name) || path.Dir(roleObj.Name.String()) != roleName.String() {
return ErrInvalidMetadata{ return ErrInvalidMetadata{
role: roleName, msg: fmt.Sprintf("delegation role %s invalid", roleObj.Name)} role: roleName, msg: fmt.Sprintf("delegation role %s invalid", roleObj.Name)}
} }
@ -99,7 +99,7 @@ func (t SignedTargets) GetValidDelegations(parent DelegationRole) []DelegationRo
// BuildDelegationRole returns a copy of a DelegationRole using the information in this SignedTargets for the specified role name. // BuildDelegationRole returns a copy of a DelegationRole using the information in this SignedTargets for the specified role name.
// Will error for invalid role name or key metadata within this SignedTargets. Path data is not validated. // Will error for invalid role name or key metadata within this SignedTargets. Path data is not validated.
func (t *SignedTargets) BuildDelegationRole(roleName string) (DelegationRole, error) { func (t *SignedTargets) BuildDelegationRole(roleName RoleName) (DelegationRole, error) {
for _, role := range t.Signed.Delegations.Roles { for _, role := range t.Signed.Delegations.Roles {
if role.Name == roleName { if role.Name == roleName {
pubKeys := make(map[string]PublicKey) pubKeys := make(map[string]PublicKey)
@ -184,7 +184,7 @@ func (t *SignedTargets) MarshalJSON() ([]byte, error) {
// TargetsFromSigned fully unpacks a Signed object into a SignedTargets, given // TargetsFromSigned fully unpacks a Signed object into a SignedTargets, given
// a role name (so it can validate the SignedTargets object) // a role name (so it can validate the SignedTargets object)
func TargetsFromSigned(s *Signed, roleName string) (*SignedTargets, error) { func TargetsFromSigned(s *Signed, roleName RoleName) (*SignedTargets, error) {
t := Targets{} t := Targets{}
if err := defaultSerializer.Unmarshal(*s.Signed, &t); err != nil { if err := defaultSerializer.Unmarshal(*s.Signed, &t); err != nil {
return nil, err return nil, err

View File

@ -41,11 +41,11 @@ func IsValidTimestampStructure(t Timestamp) error {
// from an empty map. // from an empty map.
// //
// For now sha256 is required and sha512 is not. // For now sha256 is required and sha512 is not.
if _, ok := t.Meta[CanonicalSnapshotRole].Hashes[notary.SHA256]; !ok { if _, ok := t.Meta[CanonicalSnapshotRole.String()].Hashes[notary.SHA256]; !ok {
return ErrInvalidMetadata{ return ErrInvalidMetadata{
role: CanonicalTimestampRole, msg: "missing snapshot sha256 checksum information"} role: CanonicalTimestampRole, msg: "missing snapshot sha256 checksum information"}
} }
if err := CheckValidHashStructures(t.Meta[CanonicalSnapshotRole].Hashes); err != nil { if err := CheckValidHashStructures(t.Meta[CanonicalSnapshotRole.String()].Hashes); err != nil {
return ErrInvalidMetadata{ return ErrInvalidMetadata{
role: CanonicalTimestampRole, msg: fmt.Sprintf("invalid snapshot checksum information, %v", err)} role: CanonicalTimestampRole, msg: fmt.Sprintf("invalid snapshot checksum information, %v", err)}
} }
@ -72,7 +72,7 @@ func NewTimestamp(snapshot *Signed) (*SignedTimestamp, error) {
Expires: DefaultExpires(CanonicalTimestampRole), Expires: DefaultExpires(CanonicalTimestampRole),
}, },
Meta: Files{ Meta: Files{
CanonicalSnapshotRole: snapshotMeta, CanonicalSnapshotRole.String(): snapshotMeta,
}, },
}, },
}, nil }, nil
@ -101,9 +101,9 @@ func (ts *SignedTimestamp) ToSigned() (*Signed, error) {
// GetSnapshot gets the expected snapshot metadata hashes in the timestamp metadata, // GetSnapshot gets the expected snapshot metadata hashes in the timestamp metadata,
// or nil if it doesn't exist // or nil if it doesn't exist
func (ts *SignedTimestamp) GetSnapshot() (*FileMeta, error) { func (ts *SignedTimestamp) GetSnapshot() (*FileMeta, error) {
snapshotExpected, ok := ts.Signed.Meta[CanonicalSnapshotRole] snapshotExpected, ok := ts.Signed.Meta[CanonicalSnapshotRole.String()]
if !ok { if !ok {
return nil, ErrMissingMeta{Role: CanonicalSnapshotRole} return nil, ErrMissingMeta{Role: CanonicalSnapshotRole.String()}
} }
return &snapshotExpected, nil return &snapshotExpected, nil
} }

View File

@ -1,6 +1,7 @@
package data package data
import ( import (
"bytes"
"crypto/sha256" "crypto/sha256"
"crypto/sha512" "crypto/sha512"
"crypto/subtle" "crypto/subtle"
@ -9,14 +10,61 @@ import (
"hash" "hash"
"io" "io"
"io/ioutil" "io/ioutil"
"path"
"strings" "strings"
"time" "time"
"github.com/sirupsen/logrus"
"github.com/docker/go/canonical/json" "github.com/docker/go/canonical/json"
"github.com/docker/notary" "github.com/docker/notary"
"github.com/sirupsen/logrus"
) )
// GUN type for specifying gun
type GUN string
func (g GUN) String() string {
return string(g)
}
// RoleName type for specifying role
type RoleName string
func (r RoleName) String() string {
return string(r)
}
// Parent provides the parent path role from the provided child role
func (r RoleName) Parent() RoleName {
return RoleName(path.Dir(r.String()))
}
// MetadataRoleMapToStringMap generates a map string of bytes from a map RoleName of bytes
func MetadataRoleMapToStringMap(roles map[RoleName][]byte) map[string][]byte {
metadata := make(map[string][]byte)
for k, v := range roles {
metadata[k.String()] = v
}
return metadata
}
// NewRoleList generates an array of RoleName objects from a slice of strings
func NewRoleList(roles []string) []RoleName {
var roleNames []RoleName
for _, role := range roles {
roleNames = append(roleNames, RoleName(role))
}
return roleNames
}
// RolesListToStringList generates an array of string objects from a slice of roles
func RolesListToStringList(roles []RoleName) []string {
var roleNames []string
for _, role := range roles {
roleNames = append(roleNames, role.String())
}
return roleNames
}
// SigAlgorithm for types of signatures // SigAlgorithm for types of signatures
type SigAlgorithm string type SigAlgorithm string
@ -26,6 +74,15 @@ func (k SigAlgorithm) String() string {
const defaultHashAlgorithm = "sha256" const defaultHashAlgorithm = "sha256"
// NotaryDefaultExpiries is the construct used to configure the default expiry times of
// the various role files.
var NotaryDefaultExpiries = map[RoleName]time.Duration{
CanonicalRootRole: notary.NotaryRootExpiry,
CanonicalTargetsRole: notary.NotaryTargetsExpiry,
CanonicalSnapshotRole: notary.NotarySnapshotExpiry,
CanonicalTimestampRole: notary.NotaryTimestampExpiry,
}
// Signature types // Signature types
const ( const (
EDDSASignature SigAlgorithm = "eddsa" EDDSASignature SigAlgorithm = "eddsa"
@ -45,23 +102,15 @@ const (
) )
// TUFTypes is the set of metadata types // TUFTypes is the set of metadata types
var TUFTypes = map[string]string{ var TUFTypes = map[RoleName]string{
CanonicalRootRole: "Root", CanonicalRootRole: "Root",
CanonicalTargetsRole: "Targets", CanonicalTargetsRole: "Targets",
CanonicalSnapshotRole: "Snapshot", CanonicalSnapshotRole: "Snapshot",
CanonicalTimestampRole: "Timestamp", CanonicalTimestampRole: "Timestamp",
} }
// SetTUFTypes allows one to override some or all of the default
// type names in TUF.
func SetTUFTypes(ts map[string]string) {
for k, v := range ts {
TUFTypes[k] = v
}
}
// ValidTUFType checks if the given type is valid for the role // ValidTUFType checks if the given type is valid for the role
func ValidTUFType(typ, role string) bool { func ValidTUFType(typ string, role RoleName) bool {
if ValidRole(role) { if ValidRole(role) {
// All targets delegation roles must have // All targets delegation roles must have
// the valid type is for targets. // the valid type is for targets.
@ -70,7 +119,7 @@ func ValidTUFType(typ, role string) bool {
// a type // a type
return false return false
} }
if strings.HasPrefix(role, CanonicalTargetsRole+"/") { if strings.HasPrefix(role.String(), CanonicalTargetsRole.String()+"/") {
role = CanonicalTargetsRole role = CanonicalTargetsRole
} }
} }
@ -133,6 +182,34 @@ type FileMeta struct {
Custom *json.RawMessage `json:"custom,omitempty"` Custom *json.RawMessage `json:"custom,omitempty"`
} }
// Equals returns true if the other FileMeta object is equivalent to this one
func (f FileMeta) Equals(o FileMeta) bool {
if o.Length != f.Length || len(f.Hashes) != len(f.Hashes) {
return false
}
if f.Custom == nil && o.Custom != nil || f.Custom != nil && o.Custom == nil {
return false
}
// we don't care if these are valid hashes, just that they are equal
for key, val := range f.Hashes {
if !bytes.Equal(val, o.Hashes[key]) {
return false
}
}
if f.Custom == nil && o.Custom == nil {
return true
}
fBytes, err := f.Custom.MarshalJSON()
if err != nil {
return false
}
oBytes, err := o.Custom.MarshalJSON()
if err != nil {
return false
}
return bytes.Equal(fBytes, oBytes)
}
// CheckHashes verifies all the checksums specified by the "hashes" of the payload. // CheckHashes verifies all the checksums specified by the "hashes" of the payload.
func CheckHashes(payload []byte, name string, hashes Hashes) error { func CheckHashes(payload []byte, name string, hashes Hashes) error {
cnt := 0 cnt := 0
@ -269,7 +346,7 @@ func NewDelegations() *Delegations {
} }
// These values are recommended TUF expiry times. // These values are recommended TUF expiry times.
var defaultExpiryTimes = map[string]time.Duration{ var defaultExpiryTimes = map[RoleName]time.Duration{
CanonicalRootRole: notary.Year, CanonicalRootRole: notary.Year,
CanonicalTargetsRole: 90 * notary.Day, CanonicalTargetsRole: 90 * notary.Day,
CanonicalSnapshotRole: 7 * notary.Day, CanonicalSnapshotRole: 7 * notary.Day,
@ -277,10 +354,10 @@ var defaultExpiryTimes = map[string]time.Duration{
} }
// SetDefaultExpiryTimes allows one to change the default expiries. // SetDefaultExpiryTimes allows one to change the default expiries.
func SetDefaultExpiryTimes(times map[string]time.Duration) { func SetDefaultExpiryTimes(times map[RoleName]time.Duration) {
for key, value := range times { for key, value := range times {
if _, ok := defaultExpiryTimes[key]; !ok { if _, ok := defaultExpiryTimes[key]; !ok {
logrus.Errorf("Attempted to set default expiry for an unknown role: %s", key) logrus.Errorf("Attempted to set default expiry for an unknown role: %s", key.String())
continue continue
} }
defaultExpiryTimes[key] = value defaultExpiryTimes[key] = value
@ -288,7 +365,7 @@ func SetDefaultExpiryTimes(times map[string]time.Duration) {
} }
// DefaultExpires gets the default expiry time for the given role // DefaultExpires gets the default expiry time for the given role
func DefaultExpires(role string) time.Time { func DefaultExpires(role RoleName) time.Time {
if d, ok := defaultExpiryTimes[role]; ok { if d, ok := defaultExpiryTimes[role]; ok {
return time.Now().Add(d) return time.Now().Add(d)
} }

View File

@ -10,7 +10,7 @@ import (
) )
type edCryptoKey struct { type edCryptoKey struct {
role string role data.RoleName
privKey data.PrivateKey privKey data.PrivateKey
} }
@ -28,13 +28,13 @@ func NewEd25519() *Ed25519 {
} }
// AddKey allows you to add a private key // AddKey allows you to add a private key
func (e *Ed25519) AddKey(role, gun string, k data.PrivateKey) error { func (e *Ed25519) AddKey(role data.RoleName, gun data.GUN, k data.PrivateKey) error {
e.addKey(role, k) e.addKey(role, k)
return nil return nil
} }
// addKey allows you to add a private key // addKey allows you to add a private key
func (e *Ed25519) addKey(role string, k data.PrivateKey) { func (e *Ed25519) addKey(role data.RoleName, k data.PrivateKey) {
e.keys[k.ID()] = edCryptoKey{ e.keys[k.ID()] = edCryptoKey{
role: role, role: role,
privKey: k, privKey: k,
@ -48,7 +48,7 @@ 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 data.RoleName) []string {
keyIDs := make([]string, 0, len(e.keys)) keyIDs := make([]string, 0, len(e.keys))
for id, edCryptoKey := range e.keys { for id, edCryptoKey := range e.keys {
if edCryptoKey.role == role { if edCryptoKey.role == role {
@ -59,8 +59,8 @@ func (e *Ed25519) ListKeys(role string) []string {
} }
// ListAllKeys returns the map of keys IDs to role // ListAllKeys returns the map of keys IDs to role
func (e *Ed25519) ListAllKeys() map[string]string { func (e *Ed25519) ListAllKeys() map[string]data.RoleName {
keys := make(map[string]string) keys := make(map[string]data.RoleName)
for id, edKey := range e.keys { for id, edKey := range e.keys {
keys[id] = edKey.role keys[id] = edKey.role
} }
@ -68,7 +68,7 @@ func (e *Ed25519) ListAllKeys() map[string]string {
} }
// Create generates a new key and returns the public part // Create generates a new key and returns the public part
func (e *Ed25519) Create(role, gun, algorithm string) (data.PublicKey, error) { func (e *Ed25519) Create(role data.RoleName, gun data.GUN, algorithm string) (data.PublicKey, error) {
if algorithm != data.ED25519Key { if algorithm != data.ED25519Key {
return nil, errors.New("only ED25519 supported by this cryptoservice") return nil, errors.New("only ED25519 supported by this cryptoservice")
} }
@ -103,7 +103,7 @@ func (e *Ed25519) GetKey(keyID string) data.PublicKey {
} }
// GetPrivateKey returns a single private key and role if present, based on the ID // GetPrivateKey returns a single private key and role if present, based on the ID
func (e *Ed25519) GetPrivateKey(keyID string) (data.PrivateKey, string, error) { func (e *Ed25519) GetPrivateKey(keyID string) (data.PrivateKey, data.RoleName, error) {
if k, ok := e.keys[keyID]; ok { if k, ok := e.keys[keyID]; ok {
return k.privKey, k.role, nil return k.privKey, k.role, nil
} }

View File

@ -3,6 +3,8 @@ package signed
import ( import (
"fmt" "fmt"
"strings" "strings"
"github.com/docker/notary/tuf/data"
) )
// ErrInsufficientSignatures - can not create enough signatures on a piece of // ErrInsufficientSignatures - can not create enough signatures on a piece of
@ -29,12 +31,12 @@ func (e ErrInsufficientSignatures) Error() string {
// ErrExpired indicates a piece of metadata has expired // ErrExpired indicates a piece of metadata has expired
type ErrExpired struct { type ErrExpired struct {
Role string Role data.RoleName
Expired string Expired string
} }
func (e ErrExpired) Error() string { func (e ErrExpired) Error() string {
return fmt.Sprintf("%s expired at %v", e.Role, e.Expired) return fmt.Sprintf("%s expired at %v", e.Role.String(), e.Expired)
} }
// ErrLowVersion indicates the piece of metadata has a version number lower than // ErrLowVersion indicates the piece of metadata has a version number lower than

View File

@ -1,8 +1,6 @@
package signed package signed
import ( import "github.com/docker/notary/tuf/data"
"github.com/docker/notary/tuf/data"
)
// KeyService provides management of keys locally. It will never // KeyService provides management of keys locally. It will never
// accept or provide private keys. Communication between the KeyService // accept or provide private keys. Communication between the KeyService
@ -10,17 +8,17 @@ import (
type KeyService interface { type KeyService interface {
// Create issues a new key pair and is responsible for loading // Create issues a new key pair and is responsible for loading
// the private key into the appropriate signing service. // the private key into the appropriate signing service.
Create(role, gun, algorithm string) (data.PublicKey, error) Create(role data.RoleName, gun data.GUN, algorithm string) (data.PublicKey, error)
// AddKey adds a private key to the specified role and gun // AddKey adds a private key to the specified role and gun
AddKey(role, gun string, key data.PrivateKey) error AddKey(role data.RoleName, gun data.GUN, key data.PrivateKey) error
// GetKey retrieves the public key if present, otherwise it returns nil // GetKey retrieves the public key if present, otherwise it returns nil
GetKey(keyID string) data.PublicKey GetKey(keyID string) data.PublicKey
// GetPrivateKey retrieves the private key and role if present and retrievable, // GetPrivateKey retrieves the private key and role if present and retrievable,
// otherwise it returns nil and an error // otherwise it returns nil and an error
GetPrivateKey(keyID string) (data.PrivateKey, string, error) GetPrivateKey(keyID string) (data.PrivateKey, data.RoleName, error)
// RemoveKey deletes the specified key, and returns an error only if the key // RemoveKey deletes the specified key, and returns an error only if the key
// removal fails. If the key doesn't exist, no error should be returned. // removal fails. If the key doesn't exist, no error should be returned.
@ -28,11 +26,11 @@ type KeyService interface {
// ListKeys returns a list of key IDs for the role, or an empty list or // ListKeys returns a list of key IDs for the role, or an empty list or
// nil if there are no keys. // nil if there are no keys.
ListKeys(role string) []string ListKeys(role data.RoleName) []string
// ListAllKeys returns a map of all available signing key IDs to role, or // ListAllKeys returns a map of all available signing key IDs to role, or
// an empty map or nil if there are no keys. // an empty map or nil if there are no keys.
ListAllKeys() map[string]string ListAllKeys() map[string]data.RoleName
} }
// CryptoService is deprecated and all instances of its use should be // CryptoService is deprecated and all instances of its use should be

View File

@ -14,10 +14,10 @@ package signed
import ( import (
"crypto/rand" "crypto/rand"
"github.com/sirupsen/logrus"
"github.com/docker/notary/trustmanager" "github.com/docker/notary/trustmanager"
"github.com/docker/notary/tuf/data" "github.com/docker/notary/tuf/data"
"github.com/docker/notary/tuf/utils" "github.com/docker/notary/tuf/utils"
"github.com/sirupsen/logrus"
) )
// Sign takes a data.Signed and a cryptoservice containing private keys, // Sign takes a data.Signed and a cryptoservice containing private keys,

View File

@ -9,11 +9,10 @@ import (
"encoding/pem" "encoding/pem"
"fmt" "fmt"
"math/big" "math/big"
"reflect"
"github.com/sirupsen/logrus"
"github.com/agl/ed25519" "github.com/agl/ed25519"
"github.com/docker/notary/tuf/data" "github.com/docker/notary/tuf/data"
"github.com/sirupsen/logrus"
) )
const ( const (
@ -32,24 +31,6 @@ var Verifiers = map[data.SigAlgorithm]Verifier{
data.EDDSASignature: Ed25519Verifier{}, data.EDDSASignature: Ed25519Verifier{},
} }
// RegisterVerifier provides a convenience function for init() functions
// to register additional verifiers or replace existing ones.
func RegisterVerifier(algorithm data.SigAlgorithm, v Verifier) {
curr, ok := Verifiers[algorithm]
if ok {
typOld := reflect.TypeOf(curr)
typNew := reflect.TypeOf(v)
logrus.Debugf(
"replacing already loaded verifier %s:%s with %s:%s",
typOld.PkgPath(), typOld.Name(),
typNew.PkgPath(), typNew.Name(),
)
} else {
logrus.Debug("adding verifier for: ", algorithm)
}
Verifiers[algorithm] = v
}
// Ed25519Verifier used to verify Ed25519 signatures // Ed25519Verifier used to verify Ed25519 signatures
type Ed25519Verifier struct{} type Ed25519Verifier struct{}

View File

@ -6,18 +6,16 @@ import (
"strings" "strings"
"time" "time"
"github.com/sirupsen/logrus"
"github.com/docker/go/canonical/json" "github.com/docker/go/canonical/json"
"github.com/docker/notary/tuf/data" "github.com/docker/notary/tuf/data"
"github.com/docker/notary/tuf/utils"
"github.com/sirupsen/logrus"
) )
// Various basic signing errors // Various basic signing errors
var ( var (
ErrMissingKey = errors.New("tuf: missing key")
ErrNoSignatures = errors.New("tuf: data has no signatures") ErrNoSignatures = errors.New("tuf: data has no signatures")
ErrInvalid = errors.New("tuf: signature verification failed") ErrInvalid = errors.New("tuf: signature verification failed")
ErrWrongMethod = errors.New("tuf: invalid signature type")
ErrUnknownRole = errors.New("tuf: unknown role")
ErrWrongType = errors.New("tuf: meta file has wrong type") ErrWrongType = errors.New("tuf: meta file has wrong type")
) )
@ -27,7 +25,7 @@ func IsExpired(t time.Time) bool {
} }
// VerifyExpiry returns ErrExpired if the metadata is expired // VerifyExpiry returns ErrExpired if the metadata is expired
func VerifyExpiry(s *data.SignedCommon, role string) error { func VerifyExpiry(s *data.SignedCommon, role data.RoleName) error {
if IsExpired(s.Expires) { if IsExpired(s.Expires) {
logrus.Errorf("Metadata for %s expired", role) logrus.Errorf("Metadata for %s expired", role)
return ErrExpired{Role: role, Expired: s.Expires.Format("Mon Jan 2 15:04:05 MST 2006")} return ErrExpired{Role: role, Expired: s.Expires.Format("Mon Jan 2 15:04:05 MST 2006")}
@ -101,12 +99,25 @@ func VerifySignature(msg []byte, sig *data.Signature, pk data.PublicKey) error {
method := sig.Method method := sig.Method
verifier, ok := Verifiers[method] verifier, ok := Verifiers[method]
if !ok { if !ok {
return fmt.Errorf("signing method is not supported: %s\n", sig.Method) return fmt.Errorf("signing method is not supported: %s", sig.Method)
} }
if err := verifier.Verify(pk, sig.Signature, msg); err != nil { if err := verifier.Verify(pk, sig.Signature, msg); err != nil {
return fmt.Errorf("signature was invalid\n") return fmt.Errorf("signature was invalid")
} }
sig.IsValid = true sig.IsValid = true
return nil return nil
} }
// VerifyPublicKeyMatchesPrivateKey checks if the private key and the public keys forms valid key pairs.
// Supports both x509 certificate PublicKeys and non-certificate PublicKeys
func VerifyPublicKeyMatchesPrivateKey(privKey data.PrivateKey, pubKey data.PublicKey) error {
pubKeyID, err := utils.CanonicalKeyID(pubKey)
if err != nil {
return fmt.Errorf("could not verify key pair: %v", err)
}
if privKey == nil || pubKeyID != privKey.ID() {
return fmt.Errorf("private key is nil or does not match public key")
}
return nil
}

View File

@ -5,17 +5,14 @@ import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"path"
"sort"
"strconv"
"strings" "strings"
"time" "time"
"github.com/sirupsen/logrus"
"github.com/docker/notary" "github.com/docker/notary"
"github.com/docker/notary/tuf/data" "github.com/docker/notary/tuf/data"
"github.com/docker/notary/tuf/signed" "github.com/docker/notary/tuf/signed"
"github.com/docker/notary/tuf/utils" "github.com/docker/notary/tuf/utils"
"github.com/sirupsen/logrus"
) )
// ErrSigVerifyFail - signature verification failed // ErrSigVerifyFail - signature verification failed
@ -43,7 +40,7 @@ func (e ErrLocalRootExpired) Error() string {
// the repo. This means specifically that the relevant JSON file has not // the repo. This means specifically that the relevant JSON file has not
// been loaded. // been loaded.
type ErrNotLoaded struct { type ErrNotLoaded struct {
Role string Role data.RoleName
} }
func (err ErrNotLoaded) Error() string { func (err ErrNotLoaded) Error() string {
@ -60,7 +57,7 @@ type StopWalk struct{}
// the Repo instance. // the Repo instance.
type Repo struct { type Repo struct {
Root *data.SignedRoot Root *data.SignedRoot
Targets map[string]*data.SignedTargets Targets map[data.RoleName]*data.SignedTargets
Snapshot *data.SignedSnapshot Snapshot *data.SignedSnapshot
Timestamp *data.SignedTimestamp Timestamp *data.SignedTimestamp
cryptoService signed.CryptoService cryptoService signed.CryptoService
@ -78,13 +75,13 @@ type Repo struct {
// can be nil. // can be nil.
func NewRepo(cryptoService signed.CryptoService) *Repo { func NewRepo(cryptoService signed.CryptoService) *Repo {
return &Repo{ return &Repo{
Targets: make(map[string]*data.SignedTargets), Targets: make(map[data.RoleName]*data.SignedTargets),
cryptoService: cryptoService, cryptoService: cryptoService,
} }
} }
// AddBaseKeys is used to add keys to the role in root.json // AddBaseKeys is used to add keys to the role in root.json
func (tr *Repo) AddBaseKeys(role string, keys ...data.PublicKey) error { func (tr *Repo) AddBaseKeys(role data.RoleName, keys ...data.PublicKey) error {
if tr.Root == nil { if tr.Root == nil {
return ErrNotLoaded{Role: data.CanonicalRootRole} return ErrNotLoaded{Role: data.CanonicalRootRole}
} }
@ -117,7 +114,7 @@ func (tr *Repo) AddBaseKeys(role string, keys ...data.PublicKey) error {
} }
// ReplaceBaseKeys is used to replace all keys for the given role with the new keys // ReplaceBaseKeys is used to replace all keys for the given role with the new keys
func (tr *Repo) ReplaceBaseKeys(role string, keys ...data.PublicKey) error { func (tr *Repo) ReplaceBaseKeys(role data.RoleName, keys ...data.PublicKey) error {
r, err := tr.GetBaseRole(role) r, err := tr.GetBaseRole(role)
if err != nil { if err != nil {
return err return err
@ -130,7 +127,7 @@ func (tr *Repo) ReplaceBaseKeys(role string, keys ...data.PublicKey) error {
} }
// RemoveBaseKeys is used to remove keys from the roles in root.json // RemoveBaseKeys is used to remove keys from the roles in root.json
func (tr *Repo) RemoveBaseKeys(role string, keyIDs ...string) error { func (tr *Repo) RemoveBaseKeys(role data.RoleName, keyIDs ...string) error {
if tr.Root == nil { if tr.Root == nil {
return ErrNotLoaded{Role: data.CanonicalRootRole} return ErrNotLoaded{Role: data.CanonicalRootRole}
} }
@ -153,20 +150,7 @@ func (tr *Repo) RemoveBaseKeys(role string, keyIDs ...string) error {
// also, whichever role had keys removed needs to be re-signed // also, whichever role had keys removed needs to be re-signed
// root has already been marked dirty. // root has already been marked dirty.
switch role { tr.markRoleDirty(role)
case data.CanonicalSnapshotRole:
if tr.Snapshot != nil {
tr.Snapshot.Dirty = true
}
case data.CanonicalTargetsRole:
if target, ok := tr.Targets[data.CanonicalTargetsRole]; ok {
target.Dirty = true
}
case data.CanonicalTimestampRole:
if tr.Timestamp != nil {
tr.Timestamp.Dirty = true
}
}
// determine which keys are no longer in use by any roles // determine which keys are no longer in use by any roles
for roleName, r := range tr.Root.Signed.Roles { for roleName, r := range tr.Root.Signed.Roles {
@ -193,12 +177,30 @@ func (tr *Repo) RemoveBaseKeys(role string, keyIDs ...string) error {
tr.cryptoService.RemoveKey(k) tr.cryptoService.RemoveKey(k)
} }
} }
tr.Root.Dirty = true tr.Root.Dirty = true
return nil return nil
} }
func (tr *Repo) markRoleDirty(role data.RoleName) {
switch role {
case data.CanonicalSnapshotRole:
if tr.Snapshot != nil {
tr.Snapshot.Dirty = true
}
case data.CanonicalTargetsRole:
if target, ok := tr.Targets[data.CanonicalTargetsRole]; ok {
target.Dirty = true
}
case data.CanonicalTimestampRole:
if tr.Timestamp != nil {
tr.Timestamp.Dirty = true
}
}
}
// GetBaseRole gets a base role from this repo's metadata // GetBaseRole gets a base role from this repo's metadata
func (tr *Repo) GetBaseRole(name string) (data.BaseRole, error) { func (tr *Repo) GetBaseRole(name data.RoleName) (data.BaseRole, error) {
if !data.ValidRole(name) { if !data.ValidRole(name) {
return data.BaseRole{}, data.ErrInvalidRole{Role: name, Reason: "invalid base role name"} return data.BaseRole{}, data.ErrInvalidRole{Role: name, Reason: "invalid base role name"}
} }
@ -215,7 +217,7 @@ func (tr *Repo) GetBaseRole(name string) (data.BaseRole, error) {
} }
// GetDelegationRole gets a delegation role from this repo's metadata, walking from the targets role down to the delegation itself // GetDelegationRole gets a delegation role from this repo's metadata, walking from the targets role down to the delegation itself
func (tr *Repo) GetDelegationRole(name string) (data.DelegationRole, error) { func (tr *Repo) GetDelegationRole(name data.RoleName) (data.DelegationRole, error) {
if !data.IsDelegation(name) { if !data.IsDelegation(name) {
return data.DelegationRole{}, data.ErrInvalidRole{Role: name, Reason: "invalid delegation name"} return data.DelegationRole{}, data.ErrInvalidRole{Role: name, Reason: "invalid delegation name"}
} }
@ -267,7 +269,7 @@ func (tr *Repo) GetDelegationRole(name string) (data.DelegationRole, error) {
} }
// Walk to the parent of this delegation, since that is where its role metadata exists // Walk to the parent of this delegation, since that is where its role metadata exists
err := tr.WalkTargets("", path.Dir(name), buildDelegationRoleVisitor) err := tr.WalkTargets("", name.Parent(), buildDelegationRoleVisitor)
if err != nil { if err != nil {
return data.DelegationRole{}, err return data.DelegationRole{}, err
} }
@ -306,7 +308,7 @@ func (tr *Repo) GetAllLoadedRoles() []*data.Role {
// Walk to parent, and either create or update this delegation. We can only create a new delegation if we're given keys // Walk to parent, and either create or update this delegation. We can only create a new delegation if we're given keys
// Ensure all updates are valid, by checking against parent ancestor paths and ensuring the keys meet the role threshold. // Ensure all updates are valid, by checking against parent ancestor paths and ensuring the keys meet the role threshold.
func delegationUpdateVisitor(roleName string, addKeys data.KeyList, removeKeys, addPaths, removePaths []string, clearAllPaths bool, newThreshold int) walkVisitorFunc { func delegationUpdateVisitor(roleName data.RoleName, addKeys data.KeyList, removeKeys, addPaths, removePaths []string, clearAllPaths bool, newThreshold int) walkVisitorFunc {
return func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} { return func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} {
var err error var err error
// Validate the changes underneath this restricted validRole for adding paths, reject invalid path additions // Validate the changes underneath this restricted validRole for adding paths, reject invalid path additions
@ -382,11 +384,11 @@ func delegationUpdateVisitor(roleName string, addKeys data.KeyList, removeKeys,
// a new delegation or updating an existing one. If keys are // a new delegation or updating an existing one. If keys are
// provided, the IDs will be added to the role (if they do not exist // provided, the IDs will be added to the role (if they do not exist
// there already), and the keys will be added to the targets file. // there already), and the keys will be added to the targets file.
func (tr *Repo) UpdateDelegationKeys(roleName string, addKeys data.KeyList, removeKeys []string, newThreshold int) error { func (tr *Repo) UpdateDelegationKeys(roleName data.RoleName, addKeys data.KeyList, removeKeys []string, newThreshold int) error {
if !data.IsDelegation(roleName) { if !data.IsDelegation(roleName) {
return data.ErrInvalidRole{Role: roleName, Reason: "not a valid delegated role"} return data.ErrInvalidRole{Role: roleName, Reason: "not a valid delegated role"}
} }
parent := path.Dir(roleName) parent := roleName.Parent()
if err := tr.VerifyCanSign(parent); err != nil { if err := tr.VerifyCanSign(parent); err != nil {
return err return err
@ -405,13 +407,13 @@ func (tr *Repo) UpdateDelegationKeys(roleName string, addKeys data.KeyList, remo
// Walk to the parent of this delegation, since that is where its role metadata exists // Walk to the parent of this delegation, since that is where its role metadata exists
// We do not have to verify that the walker reached its desired role in this scenario // We do not have to verify that the walker reached its desired role in this scenario
// since we've already done another walk to the parent role in VerifyCanSign, and potentially made a targets file // since we've already done another walk to the parent role in VerifyCanSign, and potentially made a targets file
return tr.WalkTargets("", parent, delegationUpdateVisitor(roleName, addKeys, removeKeys, []string{}, []string{}, false, newThreshold)) return tr.WalkTargets("", roleName.Parent(), delegationUpdateVisitor(roleName, addKeys, removeKeys, []string{}, []string{}, false, newThreshold))
} }
// PurgeDelegationKeys removes the provided canonical key IDs from all delegations // PurgeDelegationKeys removes the provided canonical key IDs from all delegations
// present in the subtree rooted at role. The role argument must be provided in a wildcard // present in the subtree rooted at role. The role argument must be provided in a wildcard
// format, i.e. targets/* would remove the key from all delegations in the repo // format, i.e. targets/* would remove the key from all delegations in the repo
func (tr *Repo) PurgeDelegationKeys(role string, removeKeys []string) error { func (tr *Repo) PurgeDelegationKeys(role data.RoleName, removeKeys []string) error {
if !data.IsWildDelegation(role) { if !data.IsWildDelegation(role) {
return data.ErrInvalidRole{ return data.ErrInvalidRole{
Role: role, Role: role,
@ -424,7 +426,7 @@ func (tr *Repo) PurgeDelegationKeys(role string, removeKeys []string) error {
removeIDs[id] = struct{}{} removeIDs[id] = struct{}{}
} }
start := path.Dir(role) start := role.Parent()
tufIDToCanon := make(map[string]string) tufIDToCanon := make(map[string]string)
purgeKeys := func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} { purgeKeys := func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} {
@ -480,11 +482,11 @@ func (tr *Repo) PurgeDelegationKeys(role string, removeKeys []string) error {
// UpdateDelegationPaths updates the appropriate delegation's paths. // UpdateDelegationPaths updates the appropriate delegation's paths.
// It is not allowed to create a new delegation. // It is not allowed to create a new delegation.
func (tr *Repo) UpdateDelegationPaths(roleName string, addPaths, removePaths []string, clearPaths bool) error { func (tr *Repo) UpdateDelegationPaths(roleName data.RoleName, addPaths, removePaths []string, clearPaths bool) error {
if !data.IsDelegation(roleName) { if !data.IsDelegation(roleName) {
return data.ErrInvalidRole{Role: roleName, Reason: "not a valid delegated role"} return data.ErrInvalidRole{Role: roleName, Reason: "not a valid delegated role"}
} }
parent := path.Dir(roleName) parent := roleName.Parent()
if err := tr.VerifyCanSign(parent); err != nil { if err := tr.VerifyCanSign(parent); err != nil {
return err return err
@ -510,12 +512,12 @@ func (tr *Repo) UpdateDelegationPaths(roleName string, addPaths, removePaths []s
// DeleteDelegation removes a delegated targets role from its parent // DeleteDelegation removes a delegated targets role from its parent
// targets object. It also deletes the delegation from the snapshot. // targets object. It also deletes the delegation from the snapshot.
// DeleteDelegation will only make use of the role Name field. // DeleteDelegation will only make use of the role Name field.
func (tr *Repo) DeleteDelegation(roleName string) error { func (tr *Repo) DeleteDelegation(roleName data.RoleName) error {
if !data.IsDelegation(roleName) { if !data.IsDelegation(roleName) {
return data.ErrInvalidRole{Role: roleName, Reason: "not a valid delegated role"} return data.ErrInvalidRole{Role: roleName, Reason: "not a valid delegated role"}
} }
parent := path.Dir(roleName) parent := roleName.Parent()
if err := tr.VerifyCanSign(parent); err != nil { if err := tr.VerifyCanSign(parent); err != nil {
return err return err
} }
@ -554,7 +556,7 @@ func (tr *Repo) DeleteDelegation(roleName string) error {
// InitRoot initializes an empty root file with the 4 core roles passed to the // InitRoot initializes an empty root file with the 4 core roles passed to the
// method, and the consistent flag. // method, and the consistent flag.
func (tr *Repo) InitRoot(root, timestamp, snapshot, targets data.BaseRole, consistent bool) error { func (tr *Repo) InitRoot(root, timestamp, snapshot, targets data.BaseRole, consistent bool) error {
rootRoles := make(map[string]*data.RootRole) rootRoles := make(map[data.RoleName]*data.RootRole)
rootKeys := make(map[string]data.PublicKey) rootKeys := make(map[string]data.PublicKey)
for _, r := range []data.BaseRole{root, timestamp, snapshot, targets} { for _, r := range []data.BaseRole{root, timestamp, snapshot, targets} {
@ -576,11 +578,11 @@ func (tr *Repo) InitRoot(root, timestamp, snapshot, targets data.BaseRole, consi
} }
// InitTargets initializes an empty targets, and returns the new empty target // InitTargets initializes an empty targets, and returns the new empty target
func (tr *Repo) InitTargets(role string) (*data.SignedTargets, error) { func (tr *Repo) InitTargets(role data.RoleName) (*data.SignedTargets, error) {
if !data.IsDelegation(role) && role != data.CanonicalTargetsRole { if !data.IsDelegation(role) && role != data.CanonicalTargetsRole {
return nil, data.ErrInvalidRole{ return nil, data.ErrInvalidRole{
Role: role, Role: role,
Reason: fmt.Sprintf("role is not a valid targets role name: %s", role), Reason: fmt.Sprintf("role is not a valid targets role name: %s", role.String()),
} }
} }
targets := data.NewTargets() targets := data.NewTargets()
@ -631,7 +633,7 @@ func (tr *Repo) InitTimestamp() error {
// TargetMeta returns the FileMeta entry for the given path in the // TargetMeta returns the FileMeta entry for the given path in the
// targets file associated with the given role. This may be nil if // targets file associated with the given role. This may be nil if
// the target isn't found in the targets file. // the target isn't found in the targets file.
func (tr Repo) TargetMeta(role, path string) *data.FileMeta { func (tr Repo) TargetMeta(role data.RoleName, path string) *data.FileMeta {
if t, ok := tr.Targets[role]; ok { if t, ok := tr.Targets[role]; ok {
if m, ok := t.Signed.Targets[path]; ok { if m, ok := t.Signed.Targets[path]; ok {
return &m return &m
@ -642,7 +644,7 @@ func (tr Repo) TargetMeta(role, path string) *data.FileMeta {
// TargetDelegations returns a slice of Roles that are valid publishers // TargetDelegations returns a slice of Roles that are valid publishers
// for the target path provided. // for the target path provided.
func (tr Repo) TargetDelegations(role, path string) []*data.Role { func (tr Repo) TargetDelegations(role data.RoleName, path string) []*data.Role {
var roles []*data.Role var roles []*data.Role
if t, ok := tr.Targets[role]; ok { if t, ok := tr.Targets[role]; ok {
for _, r := range t.Signed.Delegations.Roles { for _, r := range t.Signed.Delegations.Roles {
@ -659,7 +661,7 @@ func (tr Repo) TargetDelegations(role, path string) []*data.Role {
// enough signing keys to meet the threshold, since we want to support the use // enough signing keys to meet the threshold, since we want to support the use
// case of multiple signers for a role. It returns an error if the role doesn't // case of multiple signers for a role. It returns an error if the role doesn't
// exist or if there are no signing keys. // exist or if there are no signing keys.
func (tr *Repo) VerifyCanSign(roleName string) error { func (tr *Repo) VerifyCanSign(roleName data.RoleName) error {
var ( var (
role data.BaseRole role data.BaseRole
err error err error
@ -702,7 +704,7 @@ type walkVisitorFunc func(*data.SignedTargets, data.DelegationRole) interface{}
// WalkTargets will apply the specified visitor function to iteratively walk the targets/delegation metadata tree, // WalkTargets will apply the specified visitor function to iteratively walk the targets/delegation metadata tree,
// until receiving a StopWalk. The walk starts from the base "targets" role, and searches for the correct targetPath and/or rolePath // until receiving a StopWalk. The walk starts from the base "targets" role, and searches for the correct targetPath and/or rolePath
// to call the visitor function on. Any roles passed into skipRoles will be excluded from the walk, as well as roles in those subtrees // to call the visitor function on. Any roles passed into skipRoles will be excluded from the walk, as well as roles in those subtrees
func (tr *Repo) WalkTargets(targetPath, rolePath string, visitTargets walkVisitorFunc, skipRoles ...string) error { func (tr *Repo) WalkTargets(targetPath string, rolePath data.RoleName, visitTargets walkVisitorFunc, skipRoles ...data.RoleName) error {
// Start with the base targets role, which implicitly has the "" targets path // Start with the base targets role, which implicitly has the "" targets path
targetsRole, err := tr.GetBaseRole(data.CanonicalTargetsRole) targetsRole, err := tr.GetBaseRole(data.CanonicalTargetsRole)
if err != nil { if err != nil {
@ -728,15 +730,15 @@ func (tr *Repo) WalkTargets(targetPath, rolePath string, visitTargets walkVisito
} }
// We're at a prefix of the desired role subtree, so add its delegation role children and continue walking // We're at a prefix of the desired role subtree, so add its delegation role children and continue walking
if strings.HasPrefix(rolePath, role.Name+"/") { if strings.HasPrefix(rolePath.String(), role.Name.String()+"/") {
roles = append(roles, signedTgt.GetValidDelegations(role)...) roles = append(roles, signedTgt.GetValidDelegations(role)...)
continue continue
} }
// Determine whether to visit this role or not: // Determine whether to visit this role or not:
// If the paths validate against the specified targetPath and the rolePath is empty or is in the subtree. // If the paths validate against the specified targetPath and the role is empty or is a path in the subtree.
// Also check if we are choosing to skip visiting this role on this walk (see ListTargets and GetTargetByName priority) // Also check if we are choosing to skip visiting this role on this walk (see ListTargets and GetTargetByName priority)
if isValidPath(targetPath, role) && isAncestorRole(role.Name, rolePath) && !utils.StrSliceContains(skipRoles, role.Name) { if isValidPath(targetPath, role) && isAncestorRole(role.Name, rolePath) && !utils.RoleNameSliceContains(skipRoles, role.Name) {
// If we had matching path or role name, visit this target and determine whether or not to keep walking // If we had matching path or role name, visit this target and determine whether or not to keep walking
res := visitTargets(signedTgt, role) res := visitTargets(signedTgt, role)
switch typedRes := res.(type) { switch typedRes := res.(type) {
@ -763,8 +765,8 @@ func (tr *Repo) WalkTargets(targetPath, rolePath string, visitTargets walkVisito
// Will return true if given an empty candidateAncestor role name // Will return true if given an empty candidateAncestor role name
// The HasPrefix check is for determining whether the role name for candidateChild is a child (direct or further down the chain) // The HasPrefix check is for determining whether the role name for candidateChild is a child (direct or further down the chain)
// of candidateAncestor, for ex: candidateAncestor targets/a and candidateChild targets/a/b/c // of candidateAncestor, for ex: candidateAncestor targets/a and candidateChild targets/a/b/c
func isAncestorRole(candidateChild, candidateAncestor string) bool { func isAncestorRole(candidateChild data.RoleName, candidateAncestor data.RoleName) bool {
return candidateAncestor == "" || candidateAncestor == candidateChild || strings.HasPrefix(candidateChild, candidateAncestor+"/") return candidateAncestor.String() == "" || candidateAncestor == candidateChild || strings.HasPrefix(candidateChild.String(), candidateAncestor.String()+"/")
} }
// helper function that returns whether the delegation Role is valid against the given path // helper function that returns whether the delegation Role is valid against the given path
@ -776,11 +778,12 @@ func isValidPath(candidatePath string, delgRole data.DelegationRole) bool {
// AddTargets will attempt to add the given targets specifically to // AddTargets will attempt to add the given targets specifically to
// the directed role. If the metadata for the role doesn't exist yet, // the directed role. If the metadata for the role doesn't exist yet,
// AddTargets will create one. // AddTargets will create one.
func (tr *Repo) AddTargets(role string, targets data.Files) (data.Files, error) { func (tr *Repo) AddTargets(role data.RoleName, targets data.Files) (data.Files, error) {
err := tr.VerifyCanSign(role) cantSignErr := tr.VerifyCanSign(role)
if err != nil { if _, ok := cantSignErr.(data.ErrInvalidRole); ok {
return nil, err return nil, cantSignErr
} }
var needSign bool
// check existence of the role's metadata // check existence of the role's metadata
_, ok := tr.Targets[role] _, ok := tr.Targets[role]
@ -796,10 +799,18 @@ func (tr *Repo) AddTargets(role string, targets data.Files) (data.Files, error)
addTargetVisitor := func(targetPath string, targetMeta data.FileMeta) func(*data.SignedTargets, data.DelegationRole) interface{} { addTargetVisitor := func(targetPath string, targetMeta data.FileMeta) func(*data.SignedTargets, data.DelegationRole) interface{} {
return func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} { return func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} {
// We've already validated the role's target path in our walk, so just modify the metadata // We've already validated the role's target path in our walk, so just modify the metadata
tgt.Signed.Targets[targetPath] = targetMeta if targetMeta.Equals(tgt.Signed.Targets[targetPath]) {
tgt.Dirty = true // Also add to our new addedTargets map because this target was "added" successfully
// Also add to our new addedTargets map to keep track of every target we've added successfully addedTargets[targetPath] = targetMeta
addedTargets[targetPath] = targetMeta return StopWalk{}
}
needSign = true
if cantSignErr == nil {
tgt.Signed.Targets[targetPath] = targetMeta
tgt.Dirty = true
// Also add to our new addedTargets map to keep track of every target we've added successfully
addedTargets[targetPath] = targetMeta
}
return StopWalk{} return StopWalk{}
} }
} }
@ -807,6 +818,9 @@ func (tr *Repo) AddTargets(role string, targets data.Files) (data.Files, error)
// Walk the role tree while validating the target paths, and add all of our targets // Walk the role tree while validating the target paths, and add all of our targets
for path, target := range targets { for path, target := range targets {
tr.WalkTargets(path, role, addTargetVisitor(path, target)) tr.WalkTargets(path, role, addTargetVisitor(path, target))
if needSign && cantSignErr != nil {
return nil, cantSignErr
}
} }
if len(addedTargets) != len(targets) { if len(addedTargets) != len(targets) {
return nil, fmt.Errorf("Could not add all targets") return nil, fmt.Errorf("Could not add all targets")
@ -815,18 +829,21 @@ func (tr *Repo) AddTargets(role string, targets data.Files) (data.Files, error)
} }
// RemoveTargets removes the given target (paths) from the given target role (delegation) // RemoveTargets removes the given target (paths) from the given target role (delegation)
func (tr *Repo) RemoveTargets(role string, targets ...string) error { func (tr *Repo) RemoveTargets(role data.RoleName, targets ...string) error {
if err := tr.VerifyCanSign(role); err != nil { cantSignErr := tr.VerifyCanSign(role)
return err if _, ok := cantSignErr.(data.ErrInvalidRole); ok {
return cantSignErr
} }
var needSign bool
removeTargetVisitor := func(targetPath string) func(*data.SignedTargets, data.DelegationRole) interface{} { removeTargetVisitor := func(targetPath string) func(*data.SignedTargets, data.DelegationRole) interface{} {
return func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} { return func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} {
// We've already validated the role path in our walk, so just modify the metadata // We've already validated the role path in our walk, so just modify the metadata
// We don't check against the target path against the valid role paths because it's // We don't check against the target path against the valid role paths because it's
// possible we got into an invalid state and are trying to fix it // possible we got into an invalid state and are trying to fix it
delete(tgt.Signed.Targets, targetPath) if _, needSign = tgt.Signed.Targets[targetPath]; needSign && cantSignErr == nil {
tgt.Dirty = true delete(tgt.Signed.Targets, targetPath)
tgt.Dirty = true
}
return StopWalk{} return StopWalk{}
} }
} }
@ -836,6 +853,9 @@ func (tr *Repo) RemoveTargets(role string, targets ...string) error {
if ok { if ok {
for _, path := range targets { for _, path := range targets {
tr.WalkTargets("", role, removeTargetVisitor(path)) tr.WalkTargets("", role, removeTargetVisitor(path))
if needSign && cantSignErr != nil {
return cantSignErr
}
} }
} }
@ -843,7 +863,7 @@ func (tr *Repo) RemoveTargets(role string, targets ...string) error {
} }
// UpdateSnapshot updates the FileMeta for the given role based on the Signed object // UpdateSnapshot updates the FileMeta for the given role based on the Signed object
func (tr *Repo) UpdateSnapshot(role string, s *data.Signed) error { func (tr *Repo) UpdateSnapshot(role data.RoleName, s *data.Signed) error {
jsonData, err := json.Marshal(s) jsonData, err := json.Marshal(s)
if err != nil { if err != nil {
return err return err
@ -852,7 +872,7 @@ func (tr *Repo) UpdateSnapshot(role string, s *data.Signed) error {
if err != nil { if err != nil {
return err return err
} }
tr.Snapshot.Signed.Meta[role] = meta tr.Snapshot.Signed.Meta[role.String()] = meta
tr.Snapshot.Dirty = true tr.Snapshot.Dirty = true
return nil return nil
} }
@ -867,28 +887,18 @@ func (tr *Repo) UpdateTimestamp(s *data.Signed) error {
if err != nil { if err != nil {
return err return err
} }
tr.Timestamp.Signed.Meta[data.CanonicalSnapshotRole] = meta tr.Timestamp.Signed.Meta[data.CanonicalSnapshotRole.String()] = meta
tr.Timestamp.Dirty = true tr.Timestamp.Dirty = true
return nil return nil
} }
type versionedRootRole struct {
data.BaseRole
version int
}
type versionedRootRoles []versionedRootRole
func (v versionedRootRoles) Len() int { return len(v) }
func (v versionedRootRoles) Swap(i, j int) { v[i], v[j] = v[j], v[i] }
func (v versionedRootRoles) Less(i, j int) bool { return v[i].version < v[j].version }
// SignRoot signs the root, using all keys from the "root" role (i.e. currently trusted) // SignRoot signs the root, using all keys from the "root" role (i.e. currently trusted)
// as well as available keys used to sign the previous version, if the public part is // as well as available keys used to sign the previous version, if the public part is
// carried in tr.Root.Keys and the private key is available (i.e. probably previously // carried in tr.Root.Keys and the private key is available (i.e. probably previously
// trusted keys, to allow rollover). If there are any errors, attempt to put root // trusted keys, to allow rollover). If there are any errors, attempt to put root
// back to the way it was (so version won't be incremented, for instance). // back to the way it was (so version won't be incremented, for instance).
func (tr *Repo) SignRoot(expires time.Time) (*data.Signed, error) { // Extra signing keys can be added to support older clients
func (tr *Repo) SignRoot(expires time.Time, extraSigningKeys data.KeyList) (*data.Signed, error) {
logrus.Debug("signing root...") logrus.Debug("signing root...")
// duplicate root and attempt to modify it rather than the existing root // duplicate root and attempt to modify it rather than the existing root
@ -906,40 +916,12 @@ func (tr *Repo) SignRoot(expires time.Time) (*data.Signed, error) {
return nil, err return nil, err
} }
oldRootRoles := tr.getOldRootRoles() var rolesToSignWith []data.BaseRole
var latestSavedRole data.BaseRole // If the root role (root keys or root threshold) has changed, sign with the
rolesToSignWith := make([]data.BaseRole, 0, len(oldRootRoles)) // previous root role keys
if !tr.originalRootRole.Equals(currRoot) {
if len(oldRootRoles) > 0 {
sort.Sort(oldRootRoles)
for _, vRole := range oldRootRoles {
rolesToSignWith = append(rolesToSignWith, vRole.BaseRole)
}
latest := rolesToSignWith[len(rolesToSignWith)-1]
latestSavedRole = data.BaseRole{
Name: data.CanonicalRootRole,
Threshold: latest.Threshold,
Keys: latest.Keys,
}
}
// If the root role (root keys or root threshold) has changed, save the
// previous role under the role name "root.<n>", such that the "n" is the
// latest root.json version for which previous root role was valid.
// Also, guard against re-saving the previous role if the latest
// saved role is the same (which should not happen).
// n = root.json version of the originalRootRole (previous role)
// n+1 = root.json version of the currRoot (current role)
// n-m = root.json version of latestSavedRole (not necessarily n-1, because the
// last root rotation could have happened several root.json versions ago
if !tr.originalRootRole.Equals(currRoot) && !tr.originalRootRole.Equals(latestSavedRole) {
rolesToSignWith = append(rolesToSignWith, tr.originalRootRole) rolesToSignWith = append(rolesToSignWith, tr.originalRootRole)
latestSavedRole = tr.originalRootRole
versionName := oldRootVersionName(tempRoot.Signed.Version)
tempRoot.Signed.Roles[versionName] = &data.RootRole{
KeyIDs: latestSavedRole.ListKeyIDs(), Threshold: latestSavedRole.Threshold}
} }
tempRoot.Signed.Expires = expires tempRoot.Signed.Expires = expires
@ -950,7 +932,8 @@ func (tr *Repo) SignRoot(expires time.Time) (*data.Signed, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
signed, err = tr.sign(signed, rolesToSignWith, tr.getOptionalRootKeys(rolesToSignWith))
signed, err = tr.sign(signed, rolesToSignWith, extraSigningKeys)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -961,68 +944,12 @@ func (tr *Repo) SignRoot(expires time.Time) (*data.Signed, error) {
return signed, nil return signed, nil
} }
// get all the saved previous roles < the current root version
func (tr *Repo) getOldRootRoles() versionedRootRoles {
oldRootRoles := make(versionedRootRoles, 0, len(tr.Root.Signed.Roles))
// now go through the old roles
for roleName := range tr.Root.Signed.Roles {
// ensure that the rolename matches our format and that the version is
// not too high
if data.ValidRole(roleName) {
continue
}
nameTokens := strings.Split(roleName, ".")
if len(nameTokens) != 2 || nameTokens[0] != data.CanonicalRootRole {
continue
}
version, err := strconv.Atoi(nameTokens[1])
if err != nil || version >= tr.Root.Signed.Version {
continue
}
// ignore invalid roles, which shouldn't happen
oldRole, err := tr.Root.BuildBaseRole(roleName)
if err != nil {
continue
}
oldRootRoles = append(oldRootRoles, versionedRootRole{BaseRole: oldRole, version: version})
}
return oldRootRoles
}
// gets any extra optional root keys from the existing root.json signatures
// (because older repositories that have already done root rotation may not
// necessarily have older root roles)
func (tr *Repo) getOptionalRootKeys(signingRoles []data.BaseRole) []data.PublicKey {
oldKeysMap := make(map[string]data.PublicKey)
for _, oldSig := range tr.Root.Signatures {
if k, ok := tr.Root.Signed.Keys[oldSig.KeyID]; ok {
oldKeysMap[k.ID()] = k
}
}
for _, role := range signingRoles {
for keyID := range role.Keys {
delete(oldKeysMap, keyID)
}
}
oldKeys := make([]data.PublicKey, 0, len(oldKeysMap))
for _, key := range oldKeysMap {
oldKeys = append(oldKeys, key)
}
return oldKeys
}
func oldRootVersionName(version int) string { func oldRootVersionName(version int) string {
return fmt.Sprintf("%s.%v", data.CanonicalRootRole, version) return fmt.Sprintf("%s.%v", data.CanonicalRootRole, version)
} }
// SignTargets signs the targets file for the given top level or delegated targets role // SignTargets signs the targets file for the given top level or delegated targets role
func (tr *Repo) SignTargets(role string, expires time.Time) (*data.Signed, error) { func (tr *Repo) SignTargets(role data.RoleName, expires time.Time) (*data.Signed, error) {
logrus.Debugf("sign targets called for role %s", role) logrus.Debugf("sign targets called for role %s", role)
if _, ok := tr.Targets[role]; !ok { if _, ok := tr.Targets[role]; !ok {
return nil, data.ErrInvalidRole{ return nil, data.ErrInvalidRole{

341
vendor/github.com/docker/notary/tuf/utils/pkcs8.go generated vendored Normal file
View File

@ -0,0 +1,341 @@
// Package utils contains tuf related utility functions however this file is hard
// forked from https://github.com/youmark/pkcs8 package. It has been further modified
// based on the requirements of Notary. For converting keys into PKCS#8 format,
// original package expected *crypto.PrivateKey interface, which then type inferred
// to either *rsa.PrivateKey or *ecdsa.PrivateKey depending on the need and later
// converted to ASN.1 DER encoded form, this whole process was superfluous here as
// keys are already being kept in ASN.1 DER format wrapped in data.PrivateKey
// structure. With these changes, package has became tightly coupled with notary as
// most of the method signatures have been updated. Moreover support for ED25519
// keys has been added as well. License for original package is following:
//
// The MIT License (MIT)
//
// Copyright (c) 2014 youmark
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package utils
import (
"crypto/aes"
"crypto/cipher"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"errors"
"fmt"
"golang.org/x/crypto/pbkdf2"
"github.com/docker/notary/tuf/data"
)
// Copy from crypto/x509
var (
oidPublicKeyRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1}
oidPublicKeyDSA = asn1.ObjectIdentifier{1, 2, 840, 10040, 4, 1}
oidPublicKeyECDSA = asn1.ObjectIdentifier{1, 2, 840, 10045, 2, 1}
// crypto/x509 doesn't have support for ED25519
// http://www.oid-info.com/get/1.3.6.1.4.1.11591.15.1
oidPublicKeyED25519 = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11591, 15, 1}
)
// Copy from crypto/x509
var (
oidNamedCurveP224 = asn1.ObjectIdentifier{1, 3, 132, 0, 33}
oidNamedCurveP256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 3, 1, 7}
oidNamedCurveP384 = asn1.ObjectIdentifier{1, 3, 132, 0, 34}
oidNamedCurveP521 = asn1.ObjectIdentifier{1, 3, 132, 0, 35}
)
// Copy from crypto/x509
func oidFromNamedCurve(curve elliptic.Curve) (asn1.ObjectIdentifier, bool) {
switch curve {
case elliptic.P224():
return oidNamedCurveP224, true
case elliptic.P256():
return oidNamedCurveP256, true
case elliptic.P384():
return oidNamedCurveP384, true
case elliptic.P521():
return oidNamedCurveP521, true
}
return nil, false
}
// Unecrypted PKCS8
var (
oidPKCS5PBKDF2 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 5, 12}
oidPBES2 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 5, 13}
oidAES256CBC = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 42}
)
type ecPrivateKey struct {
Version int
PrivateKey []byte
NamedCurveOID asn1.ObjectIdentifier `asn1:"optional,explicit,tag:0"`
PublicKey asn1.BitString `asn1:"optional,explicit,tag:1"`
}
type privateKeyInfo struct {
Version int
PrivateKeyAlgorithm []asn1.ObjectIdentifier
PrivateKey []byte
}
// Encrypted PKCS8
type pbkdf2Params struct {
Salt []byte
IterationCount int
}
type pbkdf2Algorithms struct {
IDPBKDF2 asn1.ObjectIdentifier
PBKDF2Params pbkdf2Params
}
type pbkdf2Encs struct {
EncryAlgo asn1.ObjectIdentifier
IV []byte
}
type pbes2Params struct {
KeyDerivationFunc pbkdf2Algorithms
EncryptionScheme pbkdf2Encs
}
type pbes2Algorithms struct {
IDPBES2 asn1.ObjectIdentifier
PBES2Params pbes2Params
}
type encryptedPrivateKeyInfo struct {
EncryptionAlgorithm pbes2Algorithms
EncryptedData []byte
}
// pkcs8 reflects an ASN.1, PKCS#8 PrivateKey.
// copied from https://github.com/golang/go/blob/964639cc338db650ccadeafb7424bc8ebb2c0f6c/src/crypto/x509/pkcs8.go#L17
type pkcs8 struct {
Version int
Algo pkix.AlgorithmIdentifier
PrivateKey []byte
}
func parsePKCS8ToTufKey(der []byte) (data.PrivateKey, error) {
var key pkcs8
if _, err := asn1.Unmarshal(der, &key); err != nil {
if _, ok := err.(asn1.StructuralError); ok {
return nil, errors.New("could not decrypt private key")
}
return nil, err
}
if key.Algo.Algorithm.Equal(oidPublicKeyED25519) {
tufED25519PrivateKey, err := ED25519ToPrivateKey(key.PrivateKey)
if err != nil {
return nil, fmt.Errorf("could not convert ed25519.PrivateKey to data.PrivateKey: %v", err)
}
return tufED25519PrivateKey, nil
}
privKey, err := x509.ParsePKCS8PrivateKey(der)
if err != nil {
return nil, err
}
switch priv := privKey.(type) {
case *rsa.PrivateKey:
tufRSAPrivateKey, err := RSAToPrivateKey(priv)
if err != nil {
return nil, fmt.Errorf("could not convert rsa.PrivateKey to data.PrivateKey: %v", err)
}
return tufRSAPrivateKey, nil
case *ecdsa.PrivateKey:
tufECDSAPrivateKey, err := ECDSAToPrivateKey(priv)
if err != nil {
return nil, fmt.Errorf("could not convert ecdsa.PrivateKey to data.PrivateKey: %v", err)
}
return tufECDSAPrivateKey, nil
}
return nil, errors.New("unsupported key type")
}
// ParsePKCS8ToTufKey requires PKCS#8 key in DER format and returns data.PrivateKey
// Password should be provided in case of Encrypted PKCS#8 key, else it should be nil.
func ParsePKCS8ToTufKey(der []byte, password []byte) (data.PrivateKey, error) {
if password == nil {
return parsePKCS8ToTufKey(der)
}
var privKey encryptedPrivateKeyInfo
if _, err := asn1.Unmarshal(der, &privKey); err != nil {
return nil, errors.New("pkcs8: only PKCS #5 v2.0 supported")
}
if !privKey.EncryptionAlgorithm.IDPBES2.Equal(oidPBES2) {
return nil, errors.New("pkcs8: only PBES2 supported")
}
if !privKey.EncryptionAlgorithm.PBES2Params.KeyDerivationFunc.IDPBKDF2.Equal(oidPKCS5PBKDF2) {
return nil, errors.New("pkcs8: only PBKDF2 supported")
}
encParam := privKey.EncryptionAlgorithm.PBES2Params.EncryptionScheme
kdfParam := privKey.EncryptionAlgorithm.PBES2Params.KeyDerivationFunc.PBKDF2Params
switch {
case encParam.EncryAlgo.Equal(oidAES256CBC):
iv := encParam.IV
salt := kdfParam.Salt
iter := kdfParam.IterationCount
encryptedKey := privKey.EncryptedData
symkey := pbkdf2.Key(password, salt, iter, 32, sha1.New)
block, err := aes.NewCipher(symkey)
if err != nil {
return nil, err
}
mode := cipher.NewCBCDecrypter(block, iv)
mode.CryptBlocks(encryptedKey, encryptedKey)
// no need to explicitly remove padding, as ASN.1 unmarshalling will automatically discard it
key, err := parsePKCS8ToTufKey(encryptedKey)
if err != nil {
return nil, errors.New("pkcs8: incorrect password")
}
return key, nil
default:
return nil, errors.New("pkcs8: only AES-256-CBC supported")
}
}
func convertTUFKeyToPKCS8(priv data.PrivateKey) ([]byte, error) {
var pkey privateKeyInfo
switch priv.Algorithm() {
case data.RSAKey, data.RSAx509Key:
// Per RFC5958, if publicKey is present, then version is set to v2(1) else version is set to v1(0).
// But openssl set to v1 even publicKey is present
pkey.Version = 0
pkey.PrivateKeyAlgorithm = make([]asn1.ObjectIdentifier, 1)
pkey.PrivateKeyAlgorithm[0] = oidPublicKeyRSA
pkey.PrivateKey = priv.Private()
case data.ECDSAKey, data.ECDSAx509Key:
// To extract Curve value, parsing ECDSA key to *ecdsa.PrivateKey
eckey, err := x509.ParseECPrivateKey(priv.Private())
if err != nil {
return nil, err
}
oidNamedCurve, ok := oidFromNamedCurve(eckey.Curve)
if !ok {
return nil, errors.New("pkcs8: unknown elliptic curve")
}
// Per RFC5958, if publicKey is present, then version is set to v2(1) else version is set to v1(0).
// But openssl set to v1 even publicKey is present
pkey.Version = 1
pkey.PrivateKeyAlgorithm = make([]asn1.ObjectIdentifier, 2)
pkey.PrivateKeyAlgorithm[0] = oidPublicKeyECDSA
pkey.PrivateKeyAlgorithm[1] = oidNamedCurve
pkey.PrivateKey = priv.Private()
case data.ED25519Key:
pkey.Version = 0
pkey.PrivateKeyAlgorithm = make([]asn1.ObjectIdentifier, 1)
pkey.PrivateKeyAlgorithm[0] = oidPublicKeyED25519
pkey.PrivateKey = priv.Private()
default:
return nil, fmt.Errorf("algorithm %s not supported", priv.Algorithm())
}
return asn1.Marshal(pkey)
}
func convertTUFKeyToPKCS8Encrypted(priv data.PrivateKey, password []byte) ([]byte, error) {
// Convert private key into PKCS8 format
pkey, err := convertTUFKeyToPKCS8(priv)
if err != nil {
return nil, err
}
// Calculate key from password based on PKCS5 algorithm
// Use 8 byte salt, 16 byte IV, and 2048 iteration
iter := 2048
salt := make([]byte, 8)
iv := make([]byte, 16)
_, err = rand.Reader.Read(salt)
if err != nil {
return nil, err
}
_, err = rand.Reader.Read(iv)
if err != nil {
return nil, err
}
key := pbkdf2.Key(password, salt, iter, 32, sha1.New)
// Use AES256-CBC mode, pad plaintext with PKCS5 padding scheme
padding := aes.BlockSize - len(pkey)%aes.BlockSize
if padding > 0 {
n := len(pkey)
pkey = append(pkey, make([]byte, padding)...)
for i := 0; i < padding; i++ {
pkey[n+i] = byte(padding)
}
}
encryptedKey := make([]byte, len(pkey))
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
mode := cipher.NewCBCEncrypter(block, iv)
mode.CryptBlocks(encryptedKey, pkey)
pbkdf2algo := pbkdf2Algorithms{oidPKCS5PBKDF2, pbkdf2Params{salt, iter}}
pbkdf2encs := pbkdf2Encs{oidAES256CBC, iv}
pbes2algo := pbes2Algorithms{oidPBES2, pbes2Params{pbkdf2algo, pbkdf2encs}}
encryptedPkey := encryptedPrivateKeyInfo{pbes2algo, encryptedKey}
return asn1.Marshal(encryptedPkey)
}
// ConvertTUFKeyToPKCS8 converts a private key (data.Private) to PKCS#8 and returns in DER format
// if password is not nil, it would convert the Private Key to Encrypted PKCS#8.
func ConvertTUFKeyToPKCS8(priv data.PrivateKey, password []byte) ([]byte, error) {
if password == nil {
return convertTUFKeyToPKCS8(priv)
}
return convertTUFKeyToPKCS8Encrypted(priv, password)
}

View File

@ -3,36 +3,13 @@ package utils
import ( import (
"crypto/sha256" "crypto/sha256"
"crypto/sha512" "crypto/sha512"
"crypto/tls"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"io" "io"
"net/http"
"net/url"
"os"
"strings"
"github.com/docker/notary/tuf/data" "github.com/docker/notary/tuf/data"
) )
// Download does a simple download from a URL
func Download(url url.URL) (*http.Response, error) {
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{Transport: tr}
return client.Get(url.String())
}
// Upload does a simple JSON upload to a URL
func Upload(url string, body io.Reader) (*http.Response, error) {
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{Transport: tr}
return client.Post(url, "application/json", body)
}
// StrSliceContains checks if the given string appears in the slice // StrSliceContains checks if the given string appears in the slice
func StrSliceContains(ss []string, s string) bool { func StrSliceContains(ss []string, s string) bool {
for _, v := range ss { for _, v := range ss {
@ -43,23 +20,9 @@ func StrSliceContains(ss []string, s string) bool {
return false return false
} }
// StrSliceRemove removes the the given string from the slice, returning a new slice // RoleNameSliceContains checks if the given string appears in the slice
func StrSliceRemove(ss []string, s string) []string { func RoleNameSliceContains(ss []data.RoleName, s data.RoleName) bool {
res := []string{}
for _, v := range ss { for _, v := range ss {
if v != s {
res = append(res, v)
}
}
return res
}
// StrSliceContainsI checks if the given string appears in the slice
// in a case insensitive manner
func StrSliceContainsI(ss []string, s string) bool {
s = strings.ToLower(s)
for _, v := range ss {
v = strings.ToLower(v)
if v == s { if v == s {
return true return true
} }
@ -67,11 +30,15 @@ func StrSliceContainsI(ss []string, s string) bool {
return false return false
} }
// FileExists returns true if a file (or dir) exists at the given path, // RoleNameSliceRemove removes the the given RoleName from the slice, returning a new slice
// false otherwise func RoleNameSliceRemove(ss []data.RoleName, s data.RoleName) []data.RoleName {
func FileExists(path string) bool { res := []data.RoleName{}
_, err := os.Stat(path) for _, v := range ss {
return os.IsNotExist(err) if v != s {
res = append(res, v)
}
}
return res
} }
// NoopCloser is a simple Reader wrapper that does nothing when Close is // NoopCloser is a simple Reader wrapper that does nothing when Close is
@ -131,7 +98,7 @@ func RemoveUnusedKeys(t *data.SignedTargets) {
// FindRoleIndex returns the index of the role named <name> or -1 if no // FindRoleIndex returns the index of the role named <name> or -1 if no
// matching role is found. // matching role is found.
func FindRoleIndex(rs []*data.Role, name string) int { func FindRoleIndex(rs []*data.Role, name data.RoleName) int {
for i, r := range rs { for i, r := range rs {
if r.Name == name { if r.Name == name {
return i return i
@ -143,9 +110,9 @@ func FindRoleIndex(rs []*data.Role, name string) int {
// ConsistentName generates the appropriate HTTP URL path for the role, // ConsistentName generates the appropriate HTTP URL path for the role,
// based on whether the repo is marked as consistent. The RemoteStore // based on whether the repo is marked as consistent. The RemoteStore
// is responsible for adding file extensions. // is responsible for adding file extensions.
func ConsistentName(role string, hashSha256 []byte) string { func ConsistentName(role string, hashSHA256 []byte) string {
if len(hashSha256) > 0 { if len(hashSHA256) > 0 {
hash := hex.EncodeToString(hashSha256) hash := hex.EncodeToString(hashSHA256)
return fmt.Sprintf("%s.%s", role, hash) return fmt.Sprintf("%s.%s", role, hash)
} }
return role return role

View File

@ -16,16 +16,19 @@ import (
"math/big" "math/big"
"time" "time"
"github.com/sirupsen/logrus"
"github.com/agl/ed25519" "github.com/agl/ed25519"
"github.com/docker/notary" "github.com/docker/notary"
"github.com/docker/notary/tuf/data" "github.com/docker/notary/tuf/data"
"github.com/sirupsen/logrus"
) )
// CanonicalKeyID returns the ID of the public bytes version of a TUF key. // CanonicalKeyID returns the ID of the public bytes version of a TUF key.
// On regular RSA/ECDSA TUF keys, this is just the key ID. On X509 RSA/ECDSA // On regular RSA/ECDSA TUF keys, this is just the key ID. On X509 RSA/ECDSA
// TUF keys, this is the key ID of the public key part of the key in the leaf cert // TUF keys, this is the key ID of the public key part of the key in the leaf cert
func CanonicalKeyID(k data.PublicKey) (string, error) { func CanonicalKeyID(k data.PublicKey) (string, error) {
if k == nil {
return "", errors.New("public key is nil")
}
switch k.Algorithm() { switch k.Algorithm() {
case data.ECDSAx509Key, data.RSAx509Key: case data.ECDSAx509Key, data.RSAx509Key:
return X509PublicKeyID(k) return X509PublicKeyID(k)
@ -82,12 +85,9 @@ func X509PublicKeyID(certPubKey data.PublicKey) (string, error) {
return key.ID(), nil return key.ID(), nil
} }
// ParsePEMPrivateKey returns a data.PrivateKey from a PEM encoded private key. It func parseLegacyPrivateKey(block *pem.Block, passphrase string) (data.PrivateKey, error) {
// only supports RSA (PKCS#1) and attempts to decrypt using the passphrase, if encrypted. if notary.FIPSEnabled() {
func ParsePEMPrivateKey(pemBytes []byte, passphrase string) (data.PrivateKey, error) { return nil, fmt.Errorf("%s not supported in FIPS mode", block.Type)
block, _ := pem.Decode(pemBytes)
if block == nil {
return nil, errors.New("no valid private key found")
} }
var privKeyBytes []byte var privKeyBytes []byte
@ -142,6 +142,28 @@ func ParsePEMPrivateKey(pemBytes []byte, passphrase string) (data.PrivateKey, er
} }
} }
// ParsePEMPrivateKey returns a data.PrivateKey from a PEM encoded private key. It
// supports PKCS#8 as well as RSA/ECDSA (PKCS#1) only in non-FIPS mode and
// attempts to decrypt using the passphrase, if encrypted.
func ParsePEMPrivateKey(pemBytes []byte, passphrase string) (data.PrivateKey, error) {
block, _ := pem.Decode(pemBytes)
if block == nil {
return nil, errors.New("no valid private key found")
}
switch block.Type {
case "RSA PRIVATE KEY", "EC PRIVATE KEY", "ED25519 PRIVATE KEY":
return parseLegacyPrivateKey(block, passphrase)
case "ENCRYPTED PRIVATE KEY", "PRIVATE KEY":
if passphrase == "" {
return ParsePKCS8ToTufKey(block.Bytes, nil)
}
return ParsePKCS8ToTufKey(block.Bytes, []byte(passphrase))
default:
return nil, fmt.Errorf("unsupported key type %q", block.Type)
}
}
// CertToPEM is a utility function returns a PEM encoded x509 Certificate // CertToPEM is a utility function returns a PEM encoded x509 Certificate
func CertToPEM(cert *x509.Certificate) []byte { func CertToPEM(cert *x509.Certificate) []byte {
pemCert := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}) pemCert := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw})
@ -252,11 +274,31 @@ func ParsePEMPublicKey(pubKeyBytes []byte) (data.PublicKey, error) {
return nil, fmt.Errorf("invalid certificate: %v", err) return nil, fmt.Errorf("invalid certificate: %v", err)
} }
return CertToKey(cert), nil return CertToKey(cert), nil
case "PUBLIC KEY":
keyType, err := keyTypeForPublicKey(pemBlock.Bytes)
if err != nil {
return nil, err
}
return data.NewPublicKey(keyType, pemBlock.Bytes), nil
default: default:
return nil, fmt.Errorf("unsupported PEM block type %q, expected certificate", pemBlock.Type) return nil, fmt.Errorf("unsupported PEM block type %q, expected CERTIFICATE or PUBLIC KEY", pemBlock.Type)
} }
} }
func keyTypeForPublicKey(pubKeyBytes []byte) (string, error) {
pub, err := x509.ParsePKIXPublicKey(pubKeyBytes)
if err != nil {
return "", fmt.Errorf("unable to parse pem encoded public key: %v", err)
}
switch pub.(type) {
case *ecdsa.PublicKey:
return data.ECDSAKey, nil
case *rsa.PublicKey:
return data.RSAKey, nil
}
return "", fmt.Errorf("unknown public key format")
}
// ValidateCertificate returns an error if the certificate is not valid for notary // ValidateCertificate returns an error if the certificate is not valid for notary
// Currently this is only ensuring the public key has a large enough modulus if RSA, // Currently this is only ensuring the public key has a large enough modulus if RSA,
// using a non SHA1 signature algorithm, and an optional time expiry check // using a non SHA1 signature algorithm, and an optional time expiry check
@ -293,21 +335,16 @@ func ValidateCertificate(c *x509.Certificate, checkExpiry bool) error {
return nil return nil
} }
// GenerateRSAKey generates an RSA private key and returns a TUF PrivateKey // GenerateKey returns a new private key using the provided algorithm or an
func GenerateRSAKey(random io.Reader, bits int) (data.PrivateKey, error) { // error detailing why the key could not be generated
rsaPrivKey, err := rsa.GenerateKey(random, bits) func GenerateKey(algorithm string) (data.PrivateKey, error) {
if err != nil { switch algorithm {
return nil, fmt.Errorf("could not generate private key: %v", err) case data.ECDSAKey:
return GenerateECDSAKey(rand.Reader)
case data.ED25519Key:
return GenerateED25519Key(rand.Reader)
} }
return nil, fmt.Errorf("private key type not supported for key generation: %s", algorithm)
tufPrivKey, err := RSAToPrivateKey(rsaPrivKey)
if err != nil {
return nil, err
}
logrus.Debugf("generated RSA key with keyID: %s", tufPrivKey.ID())
return tufPrivKey, nil
} }
// RSAToPrivateKey converts an rsa.Private key to a TUF data.PrivateKey type // RSAToPrivateKey converts an rsa.Private key to a TUF data.PrivateKey type
@ -394,85 +431,54 @@ func ED25519ToPrivateKey(privKeyBytes []byte) (data.PrivateKey, error) {
return data.NewED25519PrivateKey(*pubKey, privKeyBytes) return data.NewED25519PrivateKey(*pubKey, privKeyBytes)
} }
func blockType(k data.PrivateKey) (string, error) { // ExtractPrivateKeyAttributes extracts role and gun values from private key bytes
switch k.Algorithm() { func ExtractPrivateKeyAttributes(pemBytes []byte) (data.RoleName, data.GUN, error) {
case data.RSAKey, data.RSAx509Key: block, _ := pem.Decode(pemBytes)
return "RSA PRIVATE KEY", nil if block == nil {
case data.ECDSAKey, data.ECDSAx509Key: return "", "", errors.New("PEM block is empty")
return "EC PRIVATE KEY", nil
case data.ED25519Key:
return "ED25519 PRIVATE KEY", nil
default:
return "", fmt.Errorf("algorithm %s not supported", k.Algorithm())
}
}
// KeyToPEM returns a PEM encoded key from a Private Key
func KeyToPEM(privKey data.PrivateKey, role string) ([]byte, error) {
bt, err := blockType(privKey)
if err != nil {
return nil, err
} }
headers := map[string]string{} switch block.Type {
if role != "" { case "RSA PRIVATE KEY", "EC PRIVATE KEY", "ED25519 PRIVATE KEY":
headers = map[string]string{ if notary.FIPSEnabled() {
"role": role, return "", "", fmt.Errorf("%s not supported in FIPS mode", block.Type)
} }
case "PRIVATE KEY", "ENCRYPTED PRIVATE KEY":
// do nothing for PKCS#8 keys
default:
return "", "", errors.New("unknown key format")
} }
return data.RoleName(block.Headers["role"]), data.GUN(block.Headers["gun"]), nil
block := &pem.Block{
Type: bt,
Headers: headers,
Bytes: privKey.Private(),
}
return pem.EncodeToMemory(block), nil
} }
// EncryptPrivateKey returns an encrypted PEM key given a Privatekey // ConvertPrivateKeyToPKCS8 converts a data.PrivateKey to PKCS#8 Format
// and a passphrase func ConvertPrivateKeyToPKCS8(key data.PrivateKey, role data.RoleName, gun data.GUN, passphrase string) ([]byte, error) {
func EncryptPrivateKey(key data.PrivateKey, role, gun, passphrase string) ([]byte, error) { var (
bt, err := blockType(key) err error
der []byte
blockType = "PRIVATE KEY"
)
if passphrase == "" {
der, err = ConvertTUFKeyToPKCS8(key, nil)
} else {
blockType = "ENCRYPTED PRIVATE KEY"
der, err = ConvertTUFKeyToPKCS8(key, []byte(passphrase))
}
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("unable to convert to PKCS8 key")
} }
password := []byte(passphrase) headers := make(map[string]string)
cipherType := x509.PEMCipherAES256 if role != "" {
headers["role"] = role.String()
encryptedPEMBlock, err := x509.EncryptPEMBlock(rand.Reader,
bt,
key.Private(),
password,
cipherType)
if err != nil {
return nil, err
} }
if encryptedPEMBlock.Headers == nil {
return nil, fmt.Errorf("unable to encrypt key - invalid PEM file produced")
}
encryptedPEMBlock.Headers["role"] = role
if gun != "" { if gun != "" {
encryptedPEMBlock.Headers["gun"] = gun headers["gun"] = gun.String()
} }
return pem.EncodeToMemory(encryptedPEMBlock), nil return pem.EncodeToMemory(&pem.Block{Bytes: der, Type: blockType, Headers: headers}), nil
}
// ReadRoleFromPEM returns the value from the role PEM header, if it exists
func ReadRoleFromPEM(pemBytes []byte) string {
pemBlock, _ := pem.Decode(pemBytes)
if pemBlock == nil || pemBlock.Headers == nil {
return ""
}
role, ok := pemBlock.Headers["role"]
if !ok {
return ""
}
return role
} }
// CertToKey transforms a single input certificate into its corresponding // CertToKey transforms a single input certificate into its corresponding
@ -527,8 +533,8 @@ func CertBundleToKey(leafCert *x509.Certificate, intCerts []*x509.Certificate) (
return newKey, nil return newKey, nil
} }
// NewCertificate returns an X509 Certificate following a template, given a GUN and validity interval. // NewCertificate returns an X509 Certificate following a template, given a Common Name and validity interval.
func NewCertificate(gun string, startTime, endTime time.Time) (*x509.Certificate, error) { func NewCertificate(commonName string, startTime, endTime time.Time) (*x509.Certificate, error) {
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
@ -539,7 +545,7 @@ func NewCertificate(gun string, startTime, endTime time.Time) (*x509.Certificate
return &x509.Certificate{ return &x509.Certificate{
SerialNumber: serialNumber, SerialNumber: serialNumber,
Subject: pkix.Name{ Subject: pkix.Name{
CommonName: gun, CommonName: commonName,
}, },
NotBefore: startTime, NotBefore: startTime,
NotAfter: endTime, NotAfter: endTime,

59
vendor/github.com/docker/notary/vendor.conf generated vendored Normal file
View File

@ -0,0 +1,59 @@
github.com/Shopify/logrus-bugsnag 6dbc35f2c30d1e37549f9673dd07912452ab28a5
github.com/sirupsen/logrus f006c2ac4710855cf0f916dd6b77acf6b048dc6e # v1.0.3
github.com/agl/ed25519 278e1ec8e8a6e017cd07577924d6766039146ced
github.com/bugsnag/bugsnag-go 13fd6b8acda029830ef9904df6b63be0a83369d0
github.com/bugsnag/panicwrap e2c28503fcd0675329da73bf48b33404db873782
github.com/bugsnag/osext 0dd3f918b21bec95ace9dc86c7e70266cfc5c702
github.com/docker/distribution edc3ab29cdff8694dd6feb85cfeb4b5f1b38ed9c
github.com/opencontainers/go-digest a6d0ee40d4207ea02364bd3b9e8e77b9159ba1eb
github.com/docker/go-connections 3ede32e2033de7505e6500d6c868c2b9ed9f169d
github.com/docker/go d30aec9fd63c35133f8f79c3412ad91a3b08be06
github.com/dvsekhvalnov/jose2go 6387d3c1f5abd8443b223577d5a7e0f4e0e5731f # v1.2
github.com/go-sql-driver/mysql a0583e0143b1624142adab07e0e97fe106d99561 # v1.3
github.com/gorilla/mux e444e69cbd2e2e3e0749a2f3c717cec491552bbf
github.com/jinzhu/gorm 5409931a1bb87e484d68d649af9367c207713ea2
github.com/jinzhu/inflection 1c35d901db3da928c72a72d8458480cc9ade058f
github.com/lib/pq 0dad96c0b94f8dee039aa40467f767467392a0af
github.com/mattn/go-sqlite3 b4142c444a8941d0d92b0b7103a24df9cd815e42 # v1.0.0
github.com/miekg/pkcs11 ba39b9c6300b7e0be41b115330145ef8afdff7d6
github.com/mitchellh/go-homedir df55a15e5ce646808815381b3db47a8c66ea62f4
github.com/prometheus/client_golang 449ccefff16c8e2b7229f6be1921ba22f62461fe
github.com/prometheus/client_model fa8ad6fec33561be4280a8f0514318c79d7f6cb6 # model-0.0.2-12-gfa8ad6f
github.com/prometheus/procfs b1afdc266f54247f5dc725544f5d351a8661f502
github.com/prometheus/common 4fdc91a58c9d3696b982e8a680f4997403132d44
github.com/golang/protobuf c3cefd437628a0b7d31b34fe44b3a7a540e98527
github.com/spf13/cobra f368244301305f414206f889b1735a54cfc8bde8
github.com/spf13/viper be5ff3e4840cf692388bde7a057595a474ef379e
golang.org/x/crypto 5bcd134fee4dd1475da17714aac19c0aa0142e2f
golang.org/x/net 6a513affb38dc9788b449d59ffed099b8de18fa0
golang.org/x/sys 739734461d1c916b6c72a63d7efda2b27edb369f
google.golang.org/grpc 708a7f9f3283aa2d4f6132d287d78683babe55c8 # v1.0.5
github.com/pkg/errors 839d9e913e063e28dfd0e6c7b7512793e0a48be9
github.com/spf13/pflag cb88ea77998c3f024757528e3305022ab50b43be
github.com/spf13/cast 4d07383ffe94b5e5a6fa3af9211374a4507a0184
gopkg.in/yaml.v2 bef53efd0c76e49e6de55ead051f886bea7e9420
gopkg.in/fatih/pool.v2 cba550ebf9bce999a02e963296d4bc7a486cb715
github.com/gorilla/context 14f550f51af52180c2eefed15e5fd18d63c0a64a
github.com/spf13/jwalterweatherman 3d60171a64319ef63c78bd45bd60e6eab1e75f8b
github.com/mitchellh/mapstructure 2caf8efc93669b6c43e0441cdc6aed17546c96f3
github.com/magiconair/properties 624009598839a9432bd97bb75552389422357723 # v1.5.3
github.com/kr/text 6807e777504f54ad073ecef66747de158294b639
github.com/kr/pretty bc9499caa0f45ee5edb2f0209fbd61fbf3d9018f # go.weekly.2011-12-22-18-gbc9499c
github.com/hailocab/go-hostpool e80d13ce29ede4452c43dea11e79b9bc8a15b478
github.com/docker/libtrust aabc10ec26b754e797f9028f4589c5b7bd90dc20
github.com/beorn7/perks b965b613227fddccbfffe13eae360ed3fa822f8d
github.com/BurntSushi/toml a368813c5e648fee92e5f6c30e3944ff9d5e8895
github.com/matttproud/golang_protobuf_extensions d0c3fe89de86839aecf2e0579c40ba3bb336a453
github.com/inconshreveable/mousetrap 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
gopkg.in/dancannon/gorethink.v3 e324d6ad938205da6c1e8a0179dc97a5b1a92185 https://github.com/docker/gorethink # v3.0.0-logrus
# dependencies of gorethink.v3
gopkg.in/gorethink/gorethink.v2 ac5be4ae8538d44ae8843b97fc9f90860cb48a85 https://github.com/docker/gorethink # v2.2.2-logrus
github.com/cenk/backoff 32cd0c5b3aef12c76ed64aaf678f6c79736be7dc # v1.0.0
# Testing requirements
github.com/stretchr/testify 089c7181b8c728499929ff09b62d3fdd8df8adff
github.com/cloudflare/cfssl 7fb22c8cba7ecaf98e4082d22d65800cf45e042a
github.com/google/certificate-transparency 0f6e3d1d1ba4d03fdaab7cd716f36255c2e48341

View File

@ -6,19 +6,3 @@ $ make generate
``` ```
Click [here](https://github.com/google/protobuf) for more information about protobuf. Click [here](https://github.com/google/protobuf) for more information about protobuf.
The `api.pb.txt` file contains merged descriptors of all defined services and messages.
Definitions present here are considered frozen after the release.
At release time, the current `api.pb.txt` file will be moved into place to
freeze the API changes for the minor version. For example, when 1.0.0 is
released, `api.pb.txt` should be moved to `1.0.txt`. Notice that we leave off
the patch number, since the API will be completely locked down for a given
patch series.
We may find that by default, protobuf descriptors are too noisy to lock down
API changes. In that case, we may filter out certain fields in the descriptors,
possibly regenerating for old versions.
This process is similar to the [process used to ensure backwards compatibility
in Go](https://github.com/golang/go/tree/master/api).

View File

@ -1,223 +1,7 @@
// Code generated by protoc-gen-gogo. // Code generated by protoc-gen-gogo.
// source: github.com/docker/swarmkit/api/ca.proto // source: ca.proto
// DO NOT EDIT! // DO NOT EDIT!
/*
Package api is a generated protocol buffer package.
It is generated from these files:
github.com/docker/swarmkit/api/ca.proto
github.com/docker/swarmkit/api/control.proto
github.com/docker/swarmkit/api/dispatcher.proto
github.com/docker/swarmkit/api/health.proto
github.com/docker/swarmkit/api/logbroker.proto
github.com/docker/swarmkit/api/objects.proto
github.com/docker/swarmkit/api/raft.proto
github.com/docker/swarmkit/api/resource.proto
github.com/docker/swarmkit/api/snapshot.proto
github.com/docker/swarmkit/api/specs.proto
github.com/docker/swarmkit/api/types.proto
github.com/docker/swarmkit/api/watch.proto
It has these top-level messages:
NodeCertificateStatusRequest
NodeCertificateStatusResponse
IssueNodeCertificateRequest
IssueNodeCertificateResponse
GetRootCACertificateRequest
GetRootCACertificateResponse
GetUnlockKeyRequest
GetUnlockKeyResponse
GetNodeRequest
GetNodeResponse
ListNodesRequest
ListNodesResponse
UpdateNodeRequest
UpdateNodeResponse
RemoveNodeRequest
RemoveNodeResponse
GetTaskRequest
GetTaskResponse
RemoveTaskRequest
RemoveTaskResponse
ListTasksRequest
ListTasksResponse
CreateServiceRequest
CreateServiceResponse
GetServiceRequest
GetServiceResponse
UpdateServiceRequest
UpdateServiceResponse
RemoveServiceRequest
RemoveServiceResponse
ListServicesRequest
ListServicesResponse
CreateNetworkRequest
CreateNetworkResponse
GetNetworkRequest
GetNetworkResponse
RemoveNetworkRequest
RemoveNetworkResponse
ListNetworksRequest
ListNetworksResponse
GetClusterRequest
GetClusterResponse
ListClustersRequest
ListClustersResponse
KeyRotation
UpdateClusterRequest
UpdateClusterResponse
GetSecretRequest
GetSecretResponse
UpdateSecretRequest
UpdateSecretResponse
ListSecretsRequest
ListSecretsResponse
CreateSecretRequest
CreateSecretResponse
RemoveSecretRequest
RemoveSecretResponse
GetConfigRequest
GetConfigResponse
UpdateConfigRequest
UpdateConfigResponse
ListConfigsRequest
ListConfigsResponse
CreateConfigRequest
CreateConfigResponse
RemoveConfigRequest
RemoveConfigResponse
SessionRequest
SessionMessage
HeartbeatRequest
HeartbeatResponse
UpdateTaskStatusRequest
UpdateTaskStatusResponse
TasksRequest
TasksMessage
AssignmentsRequest
Assignment
AssignmentChange
AssignmentsMessage
HealthCheckRequest
HealthCheckResponse
LogSubscriptionOptions
LogSelector
LogContext
LogAttr
LogMessage
SubscribeLogsRequest
SubscribeLogsMessage
ListenSubscriptionsRequest
SubscriptionMessage
PublishLogsMessage
PublishLogsResponse
Meta
Node
Service
Endpoint
Task
NetworkAttachment
Network
Cluster
Secret
Config
Resource
Extension
RaftMember
JoinRequest
JoinResponse
LeaveRequest
LeaveResponse
ProcessRaftMessageRequest
ProcessRaftMessageResponse
ResolveAddressRequest
ResolveAddressResponse
InternalRaftRequest
StoreAction
AttachNetworkRequest
AttachNetworkResponse
DetachNetworkRequest
DetachNetworkResponse
StoreSnapshot
ClusterSnapshot
Snapshot
NodeSpec
ServiceSpec
ReplicatedService
GlobalService
TaskSpec
ResourceReference
GenericRuntimeSpec
NetworkAttachmentSpec
ContainerSpec
EndpointSpec
NetworkSpec
ClusterSpec
SecretSpec
ConfigSpec
Version
IndexEntry
Annotations
NamedGenericResource
DiscreteGenericResource
GenericResource
Resources
ResourceRequirements
Platform
PluginDescription
EngineDescription
NodeDescription
NodeTLSInfo
RaftMemberStatus
NodeStatus
Image
Mount
RestartPolicy
UpdateConfig
UpdateStatus
ContainerStatus
PortStatus
TaskStatus
NetworkAttachmentConfig
IPAMConfig
PortConfig
Driver
IPAMOptions
Peer
WeightedPeer
IssuanceStatus
AcceptancePolicy
ExternalCA
CAConfig
OrchestrationConfig
TaskDefaults
DispatcherConfig
RaftConfig
EncryptionConfig
SpreadOver
PlacementPreference
Placement
JoinTokens
RootCA
Certificate
EncryptionKey
ManagerStatus
FileTarget
SecretReference
ConfigReference
BlacklistedCertificate
HealthConfig
MaybeEncryptedRecord
RootRotation
Privileges
Object
SelectBySlot
SelectByCustom
SelectBy
WatchRequest
WatchMessage
*/
package api package api
import proto "github.com/gogo/protobuf/proto" import proto "github.com/gogo/protobuf/proto"
@ -249,12 +33,6 @@ var _ = proto.Marshal
var _ = fmt.Errorf var _ = fmt.Errorf
var _ = math.Inf var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package
type NodeCertificateStatusRequest struct { type NodeCertificateStatusRequest struct {
NodeID string `protobuf:"bytes,1,opt,name=node_id,json=nodeId,proto3" json:"node_id,omitempty"` NodeID string `protobuf:"bytes,1,opt,name=node_id,json=nodeId,proto3" json:"node_id,omitempty"`
} }
@ -623,7 +401,7 @@ var _CA_serviceDesc = grpc.ServiceDesc{
}, },
}, },
Streams: []grpc.StreamDesc{}, Streams: []grpc.StreamDesc{},
Metadata: "github.com/docker/swarmkit/api/ca.proto", Metadata: "ca.proto",
} }
// Client API for NodeCA service // Client API for NodeCA service
@ -720,7 +498,7 @@ var _NodeCA_serviceDesc = grpc.ServiceDesc{
}, },
}, },
Streams: []grpc.StreamDesc{}, Streams: []grpc.StreamDesc{},
Metadata: "github.com/docker/swarmkit/api/ca.proto", Metadata: "ca.proto",
} }
func (m *NodeCertificateStatusRequest) Marshal() (dAtA []byte, err error) { func (m *NodeCertificateStatusRequest) Marshal() (dAtA []byte, err error) {
@ -2292,48 +2070,47 @@ var (
ErrIntOverflowCa = fmt.Errorf("proto: integer overflow") ErrIntOverflowCa = fmt.Errorf("proto: integer overflow")
) )
func init() { proto.RegisterFile("github.com/docker/swarmkit/api/ca.proto", fileDescriptorCa) } func init() { proto.RegisterFile("ca.proto", fileDescriptorCa) }
var fileDescriptorCa = []byte{ var fileDescriptorCa = []byte{
// 638 bytes of a gzipped FileDescriptorProto // 610 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x54, 0xc1, 0x6e, 0xd3, 0x4c, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x54, 0xcd, 0x6e, 0xd3, 0x40,
0x10, 0xee, 0xba, 0xfd, 0xd3, 0xbf, 0xd3, 0xd0, 0xa2, 0xa5, 0x95, 0x4c, 0x9a, 0x3a, 0x95, 0x39, 0x10, 0xee, 0xba, 0x25, 0x6d, 0x27, 0xa1, 0x45, 0xdb, 0x56, 0x32, 0x69, 0xea, 0x54, 0xe6, 0xd0,
0xb4, 0x20, 0x61, 0xb7, 0x01, 0x09, 0x09, 0x2e, 0x24, 0x41, 0xaa, 0x2a, 0x54, 0x84, 0xb6, 0x82, 0x72, 0xc0, 0x6d, 0x03, 0x27, 0xb8, 0x90, 0x04, 0xa9, 0x8a, 0x50, 0x11, 0xda, 0x08, 0xae, 0x95,
0x6b, 0xe5, 0x38, 0xdb, 0x74, 0x15, 0xc7, 0x6b, 0xbc, 0xeb, 0x42, 0x6e, 0x48, 0x20, 0xde, 0x00, 0xe3, 0x2c, 0xc1, 0x8a, 0xe3, 0x35, 0xde, 0x75, 0x20, 0x37, 0x24, 0x10, 0x6f, 0x80, 0xe0, 0xc4,
0xc1, 0x89, 0x47, 0xe0, 0x39, 0x2a, 0x4e, 0x48, 0x5c, 0x38, 0x55, 0xd4, 0x0f, 0xc0, 0x33, 0x20, 0x23, 0xf0, 0x1c, 0x11, 0x27, 0x24, 0x2e, 0x9c, 0x22, 0xea, 0x07, 0xe0, 0x19, 0x90, 0xd7, 0x36,
0xaf, 0x6d, 0x9a, 0xb4, 0x4e, 0x5a, 0x4e, 0xf1, 0xce, 0x7c, 0xdf, 0x37, 0x33, 0xdf, 0x4e, 0x16, 0xcd, 0x8f, 0x13, 0xca, 0xc9, 0xbb, 0xb3, 0xdf, 0xf7, 0xcd, 0xcc, 0xb7, 0xe3, 0x85, 0x35, 0xcb,
0xd6, 0xbb, 0x4c, 0x1e, 0x46, 0x6d, 0xcb, 0xe5, 0x7d, 0xbb, 0xc3, 0xdd, 0x1e, 0x0d, 0x6d, 0xf1, 0x34, 0x3c, 0x9f, 0x09, 0x86, 0x71, 0x9b, 0x59, 0x5d, 0xea, 0x1b, 0xfc, 0xb5, 0xe9, 0xf7, 0xba,
0xda, 0x09, 0xfb, 0x3d, 0x26, 0x6d, 0x27, 0x60, 0xb6, 0xeb, 0x58, 0x41, 0xc8, 0x25, 0xc7, 0x38, 0xb6, 0x30, 0xfa, 0x27, 0xc5, 0xbc, 0x18, 0x78, 0x94, 0xc7, 0x80, 0x62, 0x9e, 0x7b, 0xd4, 0x4a,
0xcd, 0x5a, 0x79, 0xd6, 0x3a, 0xda, 0xaa, 0xdc, 0xb9, 0x84, 0x2c, 0x07, 0x01, 0x15, 0x29, 0xff, 0x37, 0xdb, 0x1d, 0xd6, 0x61, 0x72, 0x79, 0x14, 0xad, 0x92, 0xe8, 0x96, 0xe7, 0x04, 0x1d, 0xdb,
0x52, 0xac, 0x08, 0xa8, 0x9b, 0x63, 0x97, 0xba, 0xbc, 0xcb, 0xd5, 0xa7, 0x9d, 0x7c, 0x65, 0xd1, 0x3d, 0x8a, 0x3f, 0x71, 0x50, 0xaf, 0x43, 0xe9, 0x09, 0x6b, 0xd3, 0x3a, 0xf5, 0x85, 0xfd, 0xc2,
0x07, 0x13, 0x14, 0x14, 0xa2, 0x1d, 0x1d, 0xd8, 0x81, 0x17, 0x75, 0x99, 0x9f, 0xfd, 0xa4, 0x44, 0xb6, 0x4c, 0x41, 0x9b, 0xc2, 0x14, 0x01, 0x27, 0xf4, 0x55, 0x40, 0xb9, 0xc0, 0xb7, 0x60, 0xd5,
0xb3, 0x05, 0xd5, 0x67, 0xbc, 0x43, 0x5b, 0x34, 0x94, 0xec, 0x80, 0xb9, 0x8e, 0xa4, 0x7b, 0xd2, 0x65, 0x6d, 0x7a, 0x6e, 0xb7, 0x55, 0xb4, 0x8f, 0x0e, 0xd7, 0x6b, 0x10, 0x8e, 0xca, 0xb9, 0x88,
0x91, 0x91, 0x20, 0xf4, 0x55, 0x44, 0x85, 0xc4, 0xb7, 0x60, 0xd6, 0xe7, 0x1d, 0xba, 0xcf, 0x3a, 0xd2, 0x78, 0x44, 0x72, 0xd1, 0x51, 0xa3, 0xad, 0x7f, 0x41, 0xb0, 0x37, 0x47, 0x85, 0x7b, 0xcc,
0x3a, 0x5a, 0x43, 0x1b, 0x73, 0x4d, 0x88, 0x4f, 0x6a, 0xa5, 0x84, 0xb2, 0xf3, 0x84, 0x94, 0x92, 0xe5, 0x14, 0xdf, 0x87, 0x1c, 0x97, 0x11, 0xa9, 0x92, 0xaf, 0xe8, 0xc6, 0x6c, 0x43, 0x46, 0x83,
0xd4, 0x4e, 0xc7, 0xfc, 0x82, 0x60, 0x75, 0x8c, 0x8a, 0x08, 0xb8, 0x2f, 0x28, 0x7e, 0x08, 0x25, 0xf3, 0xc0, 0x74, 0xad, 0x94, 0x9b, 0x30, 0x70, 0x15, 0xf2, 0xd6, 0xa5, 0xb0, 0xaa, 0x48, 0x81,
0xa1, 0x22, 0x4a, 0x65, 0xbe, 0x6e, 0x5a, 0x17, 0x2d, 0xb3, 0x76, 0x84, 0x88, 0x1c, 0xdf, 0xcd, 0x72, 0x96, 0xc0, 0x58, 0x7e, 0x32, 0xce, 0xd1, 0x7f, 0x20, 0xd8, 0x8d, 0xd4, 0xe9, 0x54, 0x95,
0xb9, 0x19, 0x03, 0x37, 0x60, 0xde, 0x3d, 0x13, 0xd6, 0x35, 0x25, 0x50, 0x2b, 0x12, 0x18, 0xaa, 0x69, 0x97, 0xf7, 0x60, 0xc5, 0x67, 0x0e, 0x95, 0xc5, 0x6d, 0x54, 0x4a, 0x59, 0xda, 0x11, 0x93,
0x4f, 0x86, 0x39, 0xe6, 0x0f, 0x04, 0x2b, 0x89, 0x3a, 0x3d, 0xd7, 0x65, 0x3e, 0xe5, 0x7d, 0x98, 0x30, 0x87, 0xd6, 0x14, 0x15, 0x11, 0x89, 0xc6, 0x37, 0x61, 0xd9, 0xe2, 0xbe, 0x2c, 0xa8, 0x50,
0x09, 0xb9, 0x47, 0x55, 0x73, 0x0b, 0xf5, 0x6a, 0x91, 0x76, 0xc2, 0x24, 0xdc, 0xa3, 0x4d, 0x4d, 0x5b, 0x0d, 0x47, 0xe5, 0xe5, 0x7a, 0x93, 0x90, 0x28, 0x86, 0xb7, 0xe1, 0x9a, 0x60, 0x5d, 0xea,
0x47, 0x44, 0xa1, 0xf1, 0x4d, 0x98, 0x76, 0x45, 0xa8, 0x1a, 0x2a, 0x37, 0x67, 0xe3, 0x93, 0xda, 0xaa, 0xcb, 0x91, 0x69, 0x24, 0xde, 0xe0, 0x33, 0x28, 0x98, 0x7d, 0xd3, 0x76, 0xcc, 0x96, 0xed,
0x74, 0x6b, 0x8f, 0x90, 0x24, 0x86, 0x97, 0xe0, 0x3f, 0xc9, 0x7b, 0xd4, 0xd7, 0xa7, 0x13, 0xd3, 0xd8, 0x62, 0xa0, 0xae, 0xc8, 0x74, 0xb7, 0xe7, 0xa5, 0x6b, 0x7a, 0xd4, 0x32, 0xaa, 0x63, 0x04,
0x48, 0x7a, 0xc0, 0xbb, 0x50, 0x76, 0x8e, 0x1c, 0xe6, 0x39, 0x6d, 0xe6, 0x31, 0x39, 0xd0, 0x67, 0x32, 0x41, 0xd7, 0x3f, 0x22, 0x28, 0x65, 0x77, 0x95, 0xb8, 0x7e, 0x95, 0xcb, 0xc3, 0x4f, 0x61,
0x54, 0xb9, 0xdb, 0xe3, 0xca, 0xed, 0x05, 0xd4, 0xb5, 0x1a, 0x43, 0x04, 0x32, 0x42, 0x37, 0x3f, 0x53, 0x82, 0x7a, 0xb4, 0xd7, 0xa2, 0x3e, 0x7f, 0x69, 0x7b, 0xb2, 0xa3, 0x8d, 0xca, 0xc1, 0xc2,
0x22, 0xa8, 0x16, 0x4f, 0x95, 0xb9, 0x7e, 0x95, 0xcb, 0xc3, 0xcf, 0x61, 0x51, 0x81, 0xfa, 0xb4, 0xba, 0xce, 0xfe, 0xc2, 0xc9, 0x46, 0xc4, 0xbf, 0xdc, 0xeb, 0x7b, 0xb0, 0x7b, 0x4a, 0x05, 0x61,
0xdf, 0xa6, 0xa1, 0x38, 0x64, 0x81, 0x9a, 0x68, 0xa1, 0xbe, 0x3e, 0xb1, 0xaf, 0xdd, 0xbf, 0x70, 0x4c, 0xd4, 0xab, 0xb3, 0x66, 0xeb, 0x0f, 0xa1, 0x94, 0x7d, 0x9c, 0x54, 0xbd, 0x3f, 0x79, 0xdf,
0xb2, 0x90, 0xf0, 0xcf, 0xce, 0xe6, 0x2a, 0xac, 0x6c, 0x53, 0x49, 0x38, 0x97, 0xad, 0xc6, 0x45, 0x51, 0xe5, 0x85, 0xc9, 0xeb, 0xdc, 0x81, 0xad, 0x53, 0x2a, 0x9e, 0xb9, 0x0e, 0xb3, 0xba, 0x8f,
0xb3, 0xcd, 0xc7, 0x50, 0x2d, 0x4e, 0x67, 0x5d, 0xaf, 0x8d, 0xde, 0x77, 0xd2, 0x79, 0x79, 0xf4, 0xe9, 0x20, 0x15, 0xf6, 0x61, 0x7b, 0x32, 0x9c, 0x08, 0xee, 0x01, 0x04, 0x32, 0x78, 0xde, 0xa5,
0x3a, 0x97, 0xe1, 0xc6, 0x36, 0x95, 0x2f, 0x7c, 0x8f, 0xbb, 0xbd, 0xa7, 0x74, 0x90, 0x0b, 0x87, 0x83, 0x44, 0x6f, 0x3d, 0x48, 0x61, 0xf8, 0x01, 0xac, 0xf6, 0xa9, 0xcf, 0x6d, 0xe6, 0x26, 0xb3,
0xb0, 0x34, 0x1a, 0xce, 0x04, 0x57, 0x01, 0x22, 0x15, 0xdc, 0xef, 0xd1, 0x41, 0xa6, 0x37, 0x17, 0xb5, 0x9b, 0xd5, 0xf8, 0xf3, 0x18, 0x52, 0x5b, 0x19, 0x8e, 0xca, 0x4b, 0x24, 0x65, 0x54, 0xde,
0xe5, 0x30, 0xfc, 0x08, 0x66, 0x8f, 0x68, 0x28, 0x18, 0xf7, 0xb3, 0xdd, 0x5a, 0x29, 0x1a, 0xfc, 0x2b, 0xa0, 0xd4, 0xab, 0xf8, 0x1d, 0x92, 0xb9, 0x67, 0x9a, 0xc2, 0x47, 0x59, 0x5a, 0x0b, 0xdc,
0x65, 0x0a, 0x69, 0xce, 0x1c, 0x9f, 0xd4, 0xa6, 0x48, 0xce, 0xa8, 0xbf, 0xd7, 0x40, 0x6b, 0x35, 0x29, 0x1e, 0x5f, 0x9d, 0x10, 0xb7, 0xa7, 0xaf, 0x7d, 0xfb, 0xfa, 0xfb, 0xb3, 0xa2, 0xdc, 0x40,
0xf0, 0x3b, 0xa4, 0x6a, 0x5f, 0x18, 0x0a, 0xdb, 0x45, 0x5a, 0x13, 0xdc, 0xa9, 0x6c, 0x5e, 0x9d, 0xf8, 0x0d, 0x14, 0xc6, 0x0d, 0xc0, 0x07, 0x73, 0xb4, 0xa6, 0x9d, 0x2b, 0x1e, 0xfe, 0x1b, 0x98,
0x90, 0x8e, 0x67, 0xfe, 0xff, 0xed, 0xeb, 0xef, 0xcf, 0x9a, 0x76, 0x1d, 0xe1, 0x37, 0x50, 0x1e, 0x24, 0xdb, 0x91, 0xc9, 0x36, 0xe1, 0xba, 0x44, 0xde, 0xe9, 0x99, 0xae, 0xd9, 0xa1, 0x7e, 0xe5,
0x36, 0x00, 0xaf, 0x8f, 0xd1, 0x3a, 0xef, 0x5c, 0x65, 0xe3, 0x72, 0x60, 0x56, 0x6c, 0x59, 0x15, 0x93, 0x02, 0x72, 0xae, 0x12, 0x2b, 0xb2, 0xa6, 0x32, 0xdb, 0x8a, 0x05, 0x7f, 0x65, 0xb6, 0x15,
0x5b, 0x84, 0x6b, 0x0a, 0x79, 0xb7, 0xef, 0xf8, 0x4e, 0x97, 0x86, 0xf5, 0x4f, 0x1a, 0xa8, 0xbd, 0x8b, 0x06, 0x7e, 0xcc, 0x8a, 0x0f, 0x08, 0x76, 0x32, 0x9f, 0x24, 0x7c, 0x3c, 0x6f, 0xac, 0xe7,
0xca, 0xac, 0x28, 0xda, 0xca, 0x62, 0x2b, 0x26, 0xfc, 0x2b, 0x8b, 0xad, 0x98, 0xb4, 0xf0, 0x43, 0xbd, 0x81, 0xc5, 0x93, 0xff, 0x60, 0x4c, 0x17, 0x52, 0x53, 0x87, 0x17, 0xda, 0xd2, 0xcf, 0x0b,
0x56, 0x7c, 0x40, 0xb0, 0x5c, 0xf8, 0x24, 0xe1, 0xcd, 0x71, 0x6b, 0x3d, 0xee, 0x0d, 0xac, 0x6c, 0x6d, 0xe9, 0x6d, 0xa8, 0xa1, 0x61, 0xa8, 0xa1, 0xef, 0xa1, 0x86, 0x7e, 0x85, 0x1a, 0x6a, 0xe5,
0xfd, 0x03, 0xe3, 0x7c, 0x23, 0x4d, 0xfd, 0xf8, 0xd4, 0x98, 0xfa, 0x79, 0x6a, 0x4c, 0xbd, 0x8d, 0xe4, 0x0b, 0x7c, 0xf7, 0x4f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x69, 0xad, 0xed, 0x8f, 0xe6, 0x05,
0x0d, 0x74, 0x1c, 0x1b, 0xe8, 0x7b, 0x6c, 0xa0, 0x5f, 0xb1, 0x81, 0xda, 0x25, 0xf5, 0x02, 0xdf, 0x00, 0x00,
0xfb, 0x13, 0x00, 0x00, 0xff, 0xff, 0xe1, 0xda, 0xca, 0xba, 0x67, 0x06, 0x00, 0x00,
} }

View File

@ -2,10 +2,10 @@ syntax = "proto3";
package docker.swarmkit.v1; package docker.swarmkit.v1;
import "github.com/docker/swarmkit/api/types.proto"; import "types.proto";
import "github.com/docker/swarmkit/api/specs.proto"; import "specs.proto";
import "gogoproto/gogo.proto"; import "gogoproto/gogo.proto";
import "github.com/docker/swarmkit/protobuf/plugin/plugin.proto"; import "plugin/plugin.proto";
// CA defines the RPC methods for requesting certificates from a CA. // CA defines the RPC methods for requesting certificates from a CA.

View File

@ -1,5 +1,5 @@
// Code generated by protoc-gen-gogo. // Code generated by protoc-gen-gogo.
// source: github.com/docker/swarmkit/api/control.proto // source: control.proto
// DO NOT EDIT! // DO NOT EDIT!
package api package api
@ -3468,7 +3468,7 @@ var _Control_serviceDesc = grpc.ServiceDesc{
}, },
}, },
Streams: []grpc.StreamDesc{}, Streams: []grpc.StreamDesc{},
Metadata: "github.com/docker/swarmkit/api/control.proto", Metadata: "control.proto",
} }
func (m *GetNodeRequest) Marshal() (dAtA []byte, err error) { func (m *GetNodeRequest) Marshal() (dAtA []byte, err error) {
@ -15953,142 +15953,140 @@ var (
ErrIntOverflowControl = fmt.Errorf("proto: integer overflow") ErrIntOverflowControl = fmt.Errorf("proto: integer overflow")
) )
func init() { proto.RegisterFile("github.com/docker/swarmkit/api/control.proto", fileDescriptorControl) } func init() { proto.RegisterFile("control.proto", fileDescriptorControl) }
var fileDescriptorControl = []byte{ var fileDescriptorControl = []byte{
// 2137 bytes of a gzipped FileDescriptorProto // 2106 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x5a, 0x4f, 0x73, 0x1b, 0x49, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x5a, 0xcd, 0x73, 0x1b, 0x49,
0x15, 0xb7, 0xfe, 0xd8, 0x92, 0x9f, 0x6c, 0xd9, 0xee, 0x38, 0xa0, 0x52, 0x82, 0x9d, 0x9a, 0x90, 0x15, 0xb7, 0x3e, 0x6c, 0xc9, 0x4f, 0xb6, 0x6c, 0xb7, 0x1d, 0x50, 0x29, 0xc1, 0x4e, 0x4d, 0x48,
0x44, 0xd9, 0x32, 0x12, 0xab, 0xb0, 0x6c, 0x58, 0x8a, 0x3f, 0x6b, 0x3b, 0x9b, 0xd5, 0x7a, 0xe3, 0xa2, 0x50, 0x41, 0x66, 0x15, 0x16, 0xc2, 0x52, 0x7c, 0xac, 0xed, 0x6c, 0x56, 0xeb, 0x8d, 0x93,
0xa4, 0xc6, 0xc9, 0x16, 0x37, 0x95, 0x2c, 0xb5, 0xbd, 0x13, 0xc9, 0x1a, 0x31, 0x33, 0xf2, 0xae, 0x1a, 0xc7, 0x5b, 0xdc, 0x54, 0xb2, 0xd4, 0x36, 0x13, 0xc9, 0x1a, 0x31, 0x33, 0xf2, 0xae, 0x8b,
0x8b, 0x0b, 0x50, 0xcb, 0x81, 0x0f, 0x40, 0x15, 0x57, 0xae, 0x1c, 0x38, 0x70, 0xe2, 0xc0, 0x07, 0x0b, 0x50, 0xcb, 0x81, 0x3f, 0x80, 0x2a, 0xae, 0x5c, 0x39, 0x70, 0xe0, 0xc4, 0x81, 0x3f, 0x20,
0x48, 0x71, 0xe2, 0xc8, 0xc9, 0xb0, 0xaa, 0x82, 0xe2, 0xc4, 0x67, 0xa0, 0xba, 0xfb, 0xf5, 0xfc, 0xc5, 0x89, 0x23, 0x27, 0xc3, 0xaa, 0x0a, 0x8a, 0x13, 0x7f, 0xc3, 0x56, 0x77, 0xbf, 0x9e, 0x2f,
0x53, 0xcf, 0x8c, 0x24, 0xab, 0xca, 0x39, 0x59, 0xd3, 0xf3, 0x7b, 0xfd, 0x5e, 0xf7, 0xfb, 0xf5, 0xf5, 0xcc, 0xe8, 0xab, 0xca, 0x7b, 0xb2, 0xa6, 0xe7, 0xf7, 0xfa, 0xbd, 0xee, 0xf7, 0xeb, 0xdf,
0x6f, 0xba, 0x5f, 0x1b, 0x76, 0x4e, 0x0d, 0xe7, 0xf3, 0xe1, 0x71, 0xb5, 0x6d, 0x9e, 0xd5, 0x3a, 0x74, 0xbf, 0x36, 0xac, 0xb6, 0xcc, 0x9e, 0x63, 0x99, 0xdd, 0x6a, 0xdf, 0x32, 0x1d, 0x93, 0x90,
0x66, 0xbb, 0x4b, 0xad, 0x9a, 0xfd, 0x45, 0xcb, 0x3a, 0xeb, 0x1a, 0x4e, 0xad, 0x35, 0x30, 0x6a, 0xb6, 0xd9, 0xea, 0x50, 0xab, 0x6a, 0x7f, 0xda, 0xb4, 0x2e, 0x3a, 0x86, 0x53, 0xbd, 0x7c, 0xa7,
0x6d, 0xb3, 0xef, 0x58, 0x66, 0xaf, 0x3a, 0xb0, 0x4c, 0xc7, 0x24, 0x44, 0x40, 0xaa, 0x12, 0x52, 0x5c, 0xb0, 0xfb, 0xb4, 0x65, 0x0b, 0x40, 0x79, 0xd5, 0x3c, 0x7d, 0x43, 0x5b, 0x8e, 0x7c, 0x2c,
0x3d, 0x7f, 0xb7, 0xfc, 0x4e, 0x42, 0x0f, 0xf6, 0x80, 0xb6, 0x6d, 0x61, 0x5f, 0x4e, 0xf2, 0x66, 0x38, 0x57, 0x7d, 0x2a, 0x1f, 0xb6, 0xce, 0xcd, 0x73, 0x93, 0xff, 0xdc, 0x65, 0xbf, 0xb0, 0x75,
0x1e, 0xbf, 0xa6, 0x6d, 0x47, 0xa2, 0x93, 0x7a, 0x76, 0x2e, 0x06, 0x54, 0x62, 0x37, 0x4f, 0xcd, 0xb3, 0xdf, 0x1d, 0x9c, 0x1b, 0xbd, 0x5d, 0xf1, 0x47, 0x34, 0x6a, 0xef, 0x42, 0xf1, 0x39, 0x75,
0x53, 0x93, 0xff, 0xac, 0xb1, 0x5f, 0xd8, 0xfa, 0x7e, 0x4c, 0x0f, 0x1c, 0x71, 0x3c, 0x3c, 0xa9, 0x8e, 0xcc, 0x36, 0xd5, 0xe9, 0x2f, 0x06, 0xd4, 0x76, 0xc8, 0x3d, 0xc8, 0xf5, 0xcc, 0x36, 0x6d,
0x0d, 0x7a, 0xc3, 0x53, 0xa3, 0x8f, 0x7f, 0x84, 0xa1, 0xf6, 0x1e, 0x14, 0x9f, 0x52, 0xe7, 0xd0, 0x18, 0xed, 0x52, 0xea, 0x6e, 0xaa, 0xb2, 0xbc, 0x07, 0xc3, 0xeb, 0x9d, 0x25, 0x86, 0xa8, 0x1f,
0xec, 0x50, 0x9d, 0xfe, 0x7c, 0x48, 0x6d, 0x87, 0xdc, 0x85, 0x5c, 0xdf, 0xec, 0xd0, 0xa6, 0xd1, 0xe8, 0x4b, 0xec, 0x55, 0xbd, 0xad, 0xfd, 0x04, 0xd6, 0x5c, 0x33, 0xbb, 0x6f, 0xf6, 0x6c, 0x4a,
0x29, 0xa5, 0xee, 0xa4, 0x2a, 0xcb, 0xbb, 0x30, 0xba, 0xdc, 0x5e, 0x62, 0x88, 0xc6, 0xbe, 0xbe, 0x1e, 0x43, 0x96, 0xbd, 0xe4, 0x46, 0x85, 0x5a, 0xa9, 0x3a, 0x3a, 0x80, 0x2a, 0xc7, 0x73, 0x94,
0xc4, 0x5e, 0x35, 0x3a, 0xda, 0x4f, 0x60, 0xcd, 0x35, 0xb3, 0x07, 0x66, 0xdf, 0xa6, 0x64, 0x07, 0xf6, 0xdf, 0x0c, 0xac, 0x7f, 0x6c, 0xd8, 0xbc, 0x0b, 0x5b, 0xba, 0xfe, 0x00, 0x72, 0x67, 0x46,
0xb2, 0xec, 0x25, 0x37, 0x2a, 0xd4, 0x4b, 0xd5, 0xf1, 0x19, 0xac, 0x72, 0x3c, 0x47, 0x69, 0xff, 0xd7, 0xa1, 0x96, 0x8d, 0xbd, 0x3c, 0x56, 0xf5, 0x12, 0x36, 0xab, 0x7e, 0x20, 0x6c, 0x74, 0x69,
0xc9, 0xc0, 0xfa, 0xa7, 0x86, 0xcd, 0xbb, 0xb0, 0xa5, 0xeb, 0x8f, 0x20, 0x77, 0x62, 0xf4, 0x1c, 0x5c, 0xfe, 0x5d, 0x06, 0x72, 0xd8, 0x48, 0xb6, 0x60, 0xb1, 0xd7, 0xbc, 0xa0, 0xac, 0xc7, 0x4c,
0x6a, 0xd9, 0xd8, 0xcb, 0x8e, 0xaa, 0x97, 0xb0, 0x59, 0xf5, 0x23, 0x61, 0xa3, 0x4b, 0xe3, 0xf2, 0x65, 0x59, 0x17, 0x0f, 0x64, 0x17, 0x0a, 0x46, 0xbb, 0xd1, 0xb7, 0xe8, 0x99, 0xf1, 0x19, 0xb5,
0x6f, 0x33, 0x90, 0xc3, 0x46, 0xb2, 0x09, 0x8b, 0xfd, 0xd6, 0x19, 0x65, 0x3d, 0x66, 0x2a, 0xcb, 0x4b, 0x69, 0xf6, 0x6e, 0xaf, 0x38, 0xbc, 0xde, 0x81, 0xfa, 0xc1, 0x2b, 0x6c, 0xd5, 0xc1, 0x68,
0xba, 0x78, 0x20, 0x35, 0x28, 0x18, 0x9d, 0xe6, 0xc0, 0xa2, 0x27, 0xc6, 0x97, 0xd4, 0x2e, 0xa5, 0xcb, 0xdf, 0xe4, 0x15, 0x2c, 0x75, 0x9b, 0xa7, 0xb4, 0x6b, 0x97, 0x32, 0x77, 0x33, 0x95, 0x42,
0xd9, 0xbb, 0xdd, 0xe2, 0xe8, 0x72, 0x1b, 0x1a, 0xfb, 0x2f, 0xb0, 0x55, 0x07, 0xa3, 0x23, 0x7f, 0xed, 0xe9, 0x24, 0x91, 0x55, 0x3f, 0xe6, 0xa6, 0xcf, 0x7a, 0x8e, 0x75, 0xa5, 0x63, 0x3f, 0xe4,
0x93, 0x17, 0xb0, 0xd4, 0x6b, 0x1d, 0xd3, 0x9e, 0x5d, 0xca, 0xdc, 0xc9, 0x54, 0x0a, 0xf5, 0xc7, 0x05, 0x14, 0x2e, 0xe8, 0xc5, 0x29, 0xb5, 0xec, 0x9f, 0x1b, 0x7d, 0xbb, 0x94, 0xbd, 0x9b, 0xa9,
0xd3, 0x44, 0x56, 0xfd, 0x94, 0x9b, 0x3e, 0xe9, 0x3b, 0xd6, 0x85, 0x8e, 0xfd, 0x90, 0x67, 0x50, 0x14, 0x6b, 0x0f, 0xa3, 0xa6, 0xed, 0xb8, 0x4f, 0x5b, 0xd5, 0x17, 0x2e, 0x7e, 0x2f, 0xbd, 0xbe,
0x38, 0xa3, 0x67, 0xc7, 0xd4, 0xb2, 0x3f, 0x37, 0x06, 0x76, 0x29, 0x7b, 0x27, 0x53, 0x29, 0xd6, 0xa0, 0xfb, 0xed, 0xc9, 0xf7, 0x60, 0xd1, 0x32, 0xbb, 0xd4, 0x2e, 0x2d, 0xf2, 0x8e, 0xee, 0x44,
0x1f, 0x44, 0x4d, 0xdb, 0xd1, 0x80, 0xb6, 0xab, 0xcf, 0x5c, 0xfc, 0x6e, 0x7a, 0x7d, 0x41, 0xf7, 0xce, 0xbf, 0xd9, 0xa5, 0xdc, 0x5a, 0xc0, 0xc9, 0x3d, 0x58, 0x65, 0x53, 0xe2, 0xcd, 0xc5, 0x12,
0xdb, 0x93, 0xef, 0xc3, 0xa2, 0x65, 0xf6, 0xa8, 0x5d, 0x5a, 0xe4, 0x1d, 0xdd, 0x8e, 0x9c, 0x7f, 0x9f, 0xa7, 0x15, 0xd6, 0x28, 0x47, 0x5f, 0xfe, 0x01, 0x14, 0x7c, 0x43, 0x20, 0xeb, 0x90, 0xe9,
0xb3, 0x47, 0xb9, 0xb5, 0x80, 0x93, 0xbb, 0xb0, 0xca, 0xa6, 0xc4, 0x9b, 0x8b, 0x25, 0x3e, 0x4f, 0xd0, 0x2b, 0x41, 0x0f, 0x9d, 0xfd, 0x64, 0xb3, 0x7c, 0xd9, 0xec, 0x0e, 0x68, 0x29, 0xcd, 0xdb,
0x2b, 0xac, 0x51, 0x8e, 0xbe, 0xfc, 0x03, 0x28, 0xf8, 0x86, 0x40, 0xd6, 0x21, 0xd3, 0xa5, 0x17, 0xc4, 0xc3, 0x7b, 0xe9, 0xa7, 0x29, 0x6d, 0x1f, 0x36, 0x7c, 0xd3, 0x82, 0x5c, 0xa9, 0xc2, 0x22,
0x82, 0x1e, 0x3a, 0xfb, 0xc9, 0x66, 0xf9, 0xbc, 0xd5, 0x1b, 0xd2, 0x52, 0x9a, 0xb7, 0x89, 0x87, 0x63, 0x81, 0x48, 0x4a, 0x1c, 0x59, 0x04, 0x4c, 0xfb, 0x53, 0x0a, 0x36, 0x4e, 0xfa, 0xed, 0xa6,
0x0f, 0xd2, 0x8f, 0x53, 0xda, 0x1e, 0x6c, 0xf8, 0xa6, 0x05, 0xb9, 0x52, 0x85, 0x45, 0xc6, 0x02, 0x43, 0x27, 0x65, 0x2a, 0xf9, 0x31, 0xac, 0x70, 0xd0, 0x25, 0xb5, 0x6c, 0xc3, 0xec, 0xf1, 0x00,
0x91, 0x94, 0x38, 0xb2, 0x08, 0x98, 0xf6, 0xc7, 0x14, 0x6c, 0xbc, 0x1a, 0x74, 0x5a, 0x0e, 0x9d, 0x0b, 0xb5, 0xdb, 0x2a, 0x8f, 0x9f, 0x08, 0x88, 0x5e, 0x60, 0x06, 0xf8, 0x40, 0xbe, 0x03, 0x59,
0x96, 0xa9, 0xe4, 0xc7, 0xb0, 0xc2, 0x41, 0xe7, 0xd4, 0xb2, 0x0d, 0xb3, 0xcf, 0x03, 0x2c, 0xd4, 0xb6, 0xec, 0x4a, 0x19, 0x6e, 0x77, 0x27, 0x2e, 0x3f, 0x3a, 0x47, 0x6a, 0x7b, 0x40, 0xfc, 0xb1,
0x6f, 0xa9, 0x3c, 0x7e, 0x26, 0x20, 0x7a, 0x81, 0x19, 0xe0, 0x03, 0xf9, 0x2e, 0x64, 0xd9, 0xc2, 0x4e, 0xb5, 0x3c, 0x8e, 0x60, 0x43, 0xa7, 0x17, 0xe6, 0xe5, 0xe4, 0xe3, 0xdd, 0x82, 0xc5, 0x33,
0x2e, 0x65, 0xb8, 0xdd, 0xed, 0xb8, 0xfc, 0xe8, 0x1c, 0xa9, 0xed, 0x02, 0xf1, 0xc7, 0x3a, 0xd3, 0xd3, 0x6a, 0x89, 0x4c, 0xe4, 0x75, 0xf1, 0xa0, 0x6d, 0x01, 0xf1, 0xf7, 0x27, 0x62, 0xc2, 0xc5,
0xf2, 0x38, 0x84, 0x0d, 0x9d, 0x9e, 0x99, 0xe7, 0xd3, 0x8f, 0x77, 0x13, 0x16, 0x4f, 0x4c, 0xab, 0xff, 0xba, 0x69, 0x77, 0x7c, 0x2e, 0x9c, 0xa6, 0xdd, 0x09, 0xb9, 0x60, 0x08, 0xe6, 0x82, 0xbd,
0x2d, 0x32, 0x91, 0xd7, 0xc5, 0x83, 0xb6, 0x09, 0xc4, 0xdf, 0x9f, 0x88, 0x09, 0x17, 0xff, 0xcb, 0x72, 0x17, 0xbf, 0x30, 0xf3, 0x46, 0xc7, 0x5e, 0xc6, 0x8d, 0x8e, 0xe3, 0x39, 0x4a, 0x7b, 0x2a,
0x96, 0xdd, 0xf5, 0xb9, 0x70, 0x5a, 0x76, 0x37, 0xe4, 0x82, 0x21, 0x98, 0x0b, 0xf6, 0xca, 0x5d, 0x47, 0x37, 0xb1, 0x6b, 0x77, 0x1c, 0x7e, 0xef, 0xda, 0xdf, 0xb2, 0x42, 0x4c, 0x58, 0xe3, 0x14,
0xfc, 0xc2, 0xcc, 0x1b, 0x1d, 0x7b, 0x19, 0x37, 0x3a, 0x8e, 0xe7, 0x28, 0xed, 0xb1, 0x1c, 0xdd, 0x62, 0xe2, 0x37, 0x1b, 0x15, 0x93, 0x7f, 0xdd, 0xa0, 0x98, 0xa8, 0x22, 0x53, 0x8a, 0xc9, 0x2e,
0xd4, 0xae, 0xdd, 0x71, 0xf8, 0xbd, 0x6b, 0x7f, 0xcd, 0x0a, 0x31, 0x61, 0x8d, 0x33, 0x88, 0x89, 0x14, 0x6c, 0x6a, 0x5d, 0x1a, 0x2d, 0xc6, 0x0e, 0x21, 0x26, 0x18, 0xc2, 0xb1, 0x68, 0xae, 0x1f,
0xdf, 0x6c, 0x5c, 0x4c, 0xfe, 0x79, 0x8d, 0x62, 0xa2, 0x8a, 0x4c, 0x29, 0x26, 0x35, 0x28, 0xd8, 0xd8, 0x3a, 0x20, 0xa4, 0xde, 0xb6, 0xc9, 0x03, 0xc8, 0x23, 0x97, 0x84, 0x62, 0x2c, 0xef, 0x15,
0xd4, 0x3a, 0x37, 0xda, 0x8c, 0x1d, 0x42, 0x4c, 0x30, 0x84, 0x23, 0xd1, 0xdc, 0xd8, 0xb7, 0x75, 0x86, 0xd7, 0x3b, 0x39, 0x41, 0x26, 0x5b, 0xcf, 0x09, 0x36, 0xd9, 0xe4, 0x43, 0x28, 0xb6, 0xa9,
0x40, 0x48, 0xa3, 0x63, 0x93, 0xfb, 0x90, 0x47, 0x2e, 0x09, 0xc5, 0x58, 0xde, 0x2d, 0x8c, 0x2e, 0x6d, 0x58, 0xb4, 0xdd, 0xb0, 0x9d, 0xa6, 0x83, 0xfa, 0x50, 0xac, 0x7d, 0x23, 0x2a, 0xc5, 0xc7,
0xb7, 0x73, 0x82, 0x4c, 0xb6, 0x9e, 0x13, 0x6c, 0xb2, 0xc9, 0xc7, 0x50, 0xec, 0x50, 0xdb, 0xb0, 0x0c, 0xc5, 0x05, 0x66, 0x15, 0x0d, 0x79, 0x8b, 0x42, 0x68, 0x72, 0xa3, 0x42, 0x43, 0xee, 0x00,
0x68, 0xa7, 0x69, 0x3b, 0x2d, 0x07, 0xf5, 0xa1, 0x58, 0xff, 0x56, 0x54, 0x8a, 0x8f, 0x18, 0x8a, 0x0c, 0xfa, 0x0d, 0xc7, 0x6c, 0xb0, 0xf5, 0x53, 0xca, 0x73, 0x0a, 0xe7, 0x07, 0xfd, 0xd7, 0xe6,
0x0b, 0xcc, 0x2a, 0x1a, 0xf2, 0x16, 0x85, 0xd0, 0xe4, 0xc6, 0x85, 0x86, 0xdc, 0x06, 0x18, 0x0e, 0x41, 0xd3, 0xa1, 0xa4, 0x0c, 0x79, 0x6b, 0xd0, 0x73, 0x0c, 0x96, 0x81, 0x65, 0x6e, 0xed, 0x3e,
0x9a, 0x8e, 0xd9, 0x64, 0xeb, 0xa7, 0x94, 0xe7, 0x14, 0xce, 0x0f, 0x07, 0x2f, 0xcd, 0xfd, 0x96, 0xcf, 0x41, 0xa2, 0x70, 0xb2, 0x3d, 0x89, 0x62, 0x9c, 0x8b, 0x95, 0x28, 0x4e, 0x42, 0x01, 0xd3,
0x43, 0x49, 0x19, 0xf2, 0xd6, 0xb0, 0xef, 0x18, 0x2c, 0x03, 0xcb, 0xdc, 0xda, 0x7d, 0x9e, 0x83, 0x0e, 0x61, 0x6b, 0xdf, 0xa2, 0x4d, 0x87, 0xe2, 0x84, 0x4b, 0x1a, 0x3e, 0x41, 0xfd, 0x10, 0x1c,
0x44, 0xe1, 0x64, 0x7b, 0x12, 0xc5, 0x38, 0x17, 0x2b, 0x51, 0x9c, 0x84, 0x02, 0xa6, 0x1d, 0xc0, 0xdc, 0x51, 0x75, 0x83, 0x16, 0x3e, 0x09, 0x39, 0x82, 0x5b, 0xa1, 0xce, 0x30, 0xaa, 0x77, 0x21,
0xe6, 0x9e, 0x45, 0x5b, 0x0e, 0xc5, 0x09, 0x97, 0x34, 0x7c, 0x84, 0xfa, 0x21, 0x38, 0xb8, 0xad, 0x87, 0x49, 0xc4, 0x0e, 0x6f, 0xc7, 0x74, 0xa8, 0x4b, 0xac, 0xf6, 0x06, 0x36, 0x9e, 0x53, 0x27,
0xea, 0x06, 0x2d, 0x7c, 0x12, 0x72, 0x08, 0x37, 0x43, 0x9d, 0x61, 0x54, 0xef, 0x41, 0x0e, 0x93, 0x14, 0xd9, 0x63, 0x00, 0x8f, 0x33, 0xb8, 0xe6, 0x56, 0x87, 0xd7, 0x3b, 0xcb, 0x2e, 0x65, 0xf4,
0x88, 0x1d, 0xde, 0x8a, 0xe9, 0x50, 0x97, 0x58, 0xed, 0x35, 0x6c, 0x3c, 0xa5, 0x4e, 0x28, 0xb2, 0x65, 0x97, 0x31, 0xe4, 0x21, 0xac, 0x19, 0x3d, 0x9b, 0x5a, 0x4e, 0xa3, 0x4d, 0xcf, 0x9a, 0x83,
0x1d, 0x00, 0x8f, 0x33, 0xb8, 0xe6, 0x56, 0x47, 0x97, 0xdb, 0xcb, 0x2e, 0x65, 0xf4, 0x65, 0x97, 0xae, 0x63, 0xa3, 0xc2, 0x14, 0x45, 0xf3, 0x01, 0xb6, 0x6a, 0x87, 0x40, 0xfc, 0xbe, 0x66, 0x0b,
0x31, 0xe4, 0x01, 0xac, 0x19, 0x7d, 0x9b, 0x5a, 0x4e, 0xb3, 0x43, 0x4f, 0x5a, 0xc3, 0x9e, 0x63, 0xfc, 0x2f, 0x69, 0xd8, 0x12, 0x62, 0x3a, 0x53, 0xf0, 0x07, 0xb0, 0x26, 0xd1, 0x13, 0x7c, 0x07,
0xa3, 0xc2, 0x14, 0x45, 0xf3, 0x3e, 0xb6, 0x6a, 0x07, 0x40, 0xfc, 0xbe, 0xae, 0x16, 0xf8, 0x9f, 0x8a, 0x68, 0x23, 0x3f, 0x05, 0x4f, 0x02, 0x9f, 0x82, 0xf1, 0x52, 0x49, 0x5e, 0x40, 0xde, 0x32,
0xd3, 0xb0, 0x29, 0xc4, 0xf4, 0x4a, 0xc1, 0xef, 0xc3, 0x9a, 0x44, 0x4f, 0xf1, 0x1d, 0x28, 0xa2, 0xbb, 0xdd, 0xd3, 0x66, 0xab, 0x53, 0xca, 0xde, 0x4d, 0x55, 0x8a, 0xb5, 0x77, 0x54, 0x86, 0xaa,
0x8d, 0xfc, 0x14, 0x3c, 0x0a, 0x7c, 0x0a, 0x26, 0x4b, 0x25, 0x79, 0x06, 0x79, 0xcb, 0xec, 0xf5, 0x41, 0x56, 0x75, 0x34, 0xd4, 0xdd, 0x2e, 0x34, 0x0d, 0xf2, 0xb2, 0x95, 0xe4, 0x21, 0x7b, 0xf4,
0x8e, 0x5b, 0xed, 0x6e, 0x29, 0x7b, 0x27, 0x55, 0x29, 0xd6, 0xdf, 0x55, 0x19, 0xaa, 0x06, 0x59, 0xf2, 0xe8, 0xd9, 0xfa, 0x02, 0x59, 0x81, 0xfc, 0x2b, 0xfd, 0xd9, 0x27, 0xf5, 0x97, 0x27, 0xc7,
0xd5, 0xd1, 0x50, 0x77, 0xbb, 0xd0, 0x34, 0xc8, 0xcb, 0x56, 0x92, 0x87, 0xec, 0xe1, 0xf3, 0xc3, 0xeb, 0x29, 0xc6, 0x9e, 0x50, 0x77, 0xb3, 0x25, 0xe1, 0x00, 0xb6, 0x84, 0xe8, 0xce, 0x92, 0x03,
0x27, 0xeb, 0x0b, 0x64, 0x05, 0xf2, 0x2f, 0xf4, 0x27, 0x9f, 0x35, 0x9e, 0xbf, 0x3a, 0x5a, 0x4f, 0xed, 0xeb, 0x70, 0x2b, 0xd4, 0x0b, 0xaa, 0xf7, 0xe7, 0x19, 0xd8, 0x64, 0xeb, 0x0f, 0xdb, 0x5d,
0x31, 0xf6, 0x84, 0xba, 0xbb, 0x5a, 0x12, 0xf6, 0x61, 0x53, 0x88, 0xee, 0x55, 0x72, 0xa0, 0x7d, 0x01, 0xaf, 0x87, 0x05, 0x7c, 0x37, 0x4a, 0x26, 0x43, 0x96, 0xa3, 0x1a, 0xfe, 0xc7, 0xf4, 0xdc,
0x13, 0x6e, 0x86, 0x7a, 0x41, 0xf5, 0xfe, 0x2a, 0x03, 0x37, 0xd8, 0xfa, 0xc3, 0x76, 0x57, 0xc0, 0x35, 0xfc, 0x38, 0xa4, 0xe1, 0x3f, 0x9c, 0x30, 0x38, 0xa5, 0x8c, 0x8f, 0x68, 0x64, 0x56, 0xa1,
0x1b, 0x61, 0x01, 0xaf, 0x45, 0xc9, 0x64, 0xc8, 0x72, 0x5c, 0xc3, 0xff, 0x90, 0x9e, 0xbb, 0x86, 0x91, 0x7e, 0x15, 0x5c, 0x9c, 0x9f, 0x0a, 0xbe, 0x84, 0xad, 0x60, 0xb8, 0x48, 0x9a, 0xef, 0x43,
0x1f, 0x85, 0x34, 0xfc, 0x87, 0x53, 0x06, 0xa7, 0x94, 0xf1, 0x31, 0x8d, 0xcc, 0x2a, 0x34, 0xd2, 0x1e, 0x93, 0x28, 0xb5, 0x30, 0x96, 0x35, 0x2e, 0xd8, 0x53, 0xc4, 0x23, 0xea, 0x7c, 0x6a, 0x5a,
0xaf, 0x82, 0x8b, 0xf3, 0x53, 0xc1, 0xe7, 0xb0, 0x19, 0x0c, 0x17, 0x49, 0xf3, 0x3e, 0xe4, 0x31, 0x9d, 0x09, 0x14, 0x11, 0x2d, 0x54, 0x8a, 0xe8, 0x76, 0xe6, 0x71, 0xba, 0x27, 0x9a, 0xe2, 0x38,
0x89, 0x52, 0x0b, 0x63, 0x59, 0xe3, 0x82, 0x3d, 0x45, 0x3c, 0xa4, 0xce, 0x17, 0xa6, 0xd5, 0x9d, 0x2d, 0xad, 0x24, 0x56, 0x3b, 0xe1, 0x8a, 0x18, 0x8a, 0x8c, 0x40, 0x96, 0xcd, 0x34, 0xce, 0x17,
0x42, 0x11, 0xd1, 0x42, 0xa5, 0x88, 0x6e, 0x67, 0x1e, 0xa7, 0xfb, 0xa2, 0x29, 0x8e, 0xd3, 0xd2, 0xff, 0xcd, 0x48, 0x8e, 0x36, 0x8c, 0xe4, 0x69, 0x8f, 0xe4, 0x68, 0xcb, 0x48, 0x8e, 0x80, 0x7a,
0x4a, 0x62, 0xb5, 0x57, 0x5c, 0x11, 0x43, 0x91, 0x11, 0xc8, 0xb2, 0x99, 0xc6, 0xf9, 0xe2, 0xbf, 0x1b, 0xc5, 0x6f, 0x4e, 0x31, 0xfe, 0x4c, 0xae, 0xbb, 0xb9, 0x87, 0xe9, 0xae, 0xc5, 0x50, 0xa4,
0x19, 0xc9, 0xd1, 0x86, 0x91, 0x3c, 0xed, 0x91, 0x1c, 0x6d, 0x19, 0xc9, 0x11, 0xd0, 0xe8, 0xa0, 0xda, 0xff, 0xd2, 0x62, 0x2d, 0x62, 0xfb, 0x14, 0x6b, 0x31, 0x64, 0x39, 0xba, 0x16, 0x7f, 0x7b,
0xf8, 0xcd, 0x29, 0xc6, 0x9f, 0xc9, 0x75, 0x37, 0xf7, 0x30, 0xdd, 0xb5, 0x18, 0x8a, 0x54, 0xfb, 0x83, 0x6b, 0x31, 0x22, 0xb8, 0xa9, 0xd7, 0xe2, 0x1c, 0xd6, 0x9b, 0x17, 0x92, 0xb7, 0xde, 0x30,
0x6f, 0x5a, 0xac, 0x45, 0x6c, 0x9f, 0x61, 0x2d, 0x86, 0x2c, 0xc7, 0xd7, 0xe2, 0x6f, 0xae, 0x71, 0x51, 0xb1, 0xeb, 0x4d, 0x66, 0xce, 0x05, 0x6b, 0xef, 0x73, 0x4a, 0xef, 0x77, 0x07, 0xb6, 0x43,
0x2d, 0x46, 0x04, 0x37, 0xf3, 0x5a, 0x9c, 0xc3, 0x7a, 0xf3, 0x42, 0xf2, 0xd6, 0x1b, 0x26, 0x2a, 0x2d, 0x9f, 0x46, 0xb7, 0x44, 0x4b, 0x48, 0xa3, 0x11, 0xc7, 0x78, 0x81, 0x00, 0x97, 0xbe, 0x6e,
0x76, 0xbd, 0xc9, 0xcc, 0xb9, 0x60, 0xed, 0x43, 0x4e, 0xe9, 0xbd, 0xde, 0xd0, 0x76, 0xa8, 0xe5, 0x17, 0x1e, 0x7d, 0x11, 0x12, 0x47, 0x5f, 0x69, 0x25, 0xb1, 0x2e, 0x97, 0xf0, 0xc5, 0x14, 0x5c,
0xd3, 0xe8, 0xb6, 0x68, 0x09, 0x69, 0x34, 0xe2, 0x18, 0x2f, 0x10, 0xe0, 0xd2, 0xd7, 0xed, 0xc2, 0x0a, 0x59, 0x7e, 0xb5, 0xb8, 0x14, 0x11, 0xdc, 0x4d, 0x72, 0xc9, 0x0b, 0xc9, 0xe3, 0x12, 0x66,
0xa3, 0x2f, 0x42, 0xe2, 0xe8, 0x2b, 0xad, 0x24, 0xd6, 0xe5, 0x12, 0xbe, 0x98, 0x81, 0x4b, 0x21, 0x23, 0x96, 0x4b, 0x32, 0x75, 0x2e, 0x58, 0xfb, 0x7d, 0x0a, 0x0a, 0x87, 0xf4, 0x4a, 0x37, 0x9d,
0xcb, 0xb7, 0x8b, 0x4b, 0x11, 0xc1, 0x5d, 0x27, 0x97, 0xbc, 0x90, 0x3c, 0x2e, 0x61, 0x36, 0x62, 0xa6, 0xc3, 0xb6, 0x3e, 0xdf, 0x82, 0x0d, 0x46, 0x32, 0x6a, 0x35, 0xde, 0x98, 0x46, 0xaf, 0xe1,
0xb9, 0x24, 0x53, 0xe7, 0x82, 0xb5, 0xdf, 0xa5, 0xa0, 0x70, 0x40, 0x2f, 0x74, 0xd3, 0x69, 0x39, 0x98, 0x1d, 0xda, 0xe3, 0xa1, 0xe5, 0xf5, 0x35, 0xf1, 0xe2, 0x23, 0xd3, 0xe8, 0xbd, 0x66, 0xcd,
0x6c, 0xeb, 0xf3, 0x0e, 0x6c, 0x30, 0x92, 0x51, 0xab, 0xf9, 0xda, 0x34, 0xfa, 0x4d, 0xc7, 0xec, 0xe4, 0x31, 0x90, 0x8b, 0x66, 0xaf, 0x79, 0x1e, 0x04, 0x8b, 0xcd, 0xe2, 0x3a, 0xbe, 0x51, 0xa2,
0xd2, 0x3e, 0x0f, 0x2d, 0xaf, 0xaf, 0x89, 0x17, 0x9f, 0x98, 0x46, 0xff, 0x25, 0x6b, 0x26, 0x3b, 0x07, 0xbd, 0xae, 0xd9, 0xea, 0x34, 0xd8, 0xa8, 0x33, 0x01, 0xf4, 0x09, 0x7f, 0x71, 0x48, 0xaf,
0x40, 0xce, 0x5a, 0xfd, 0xd6, 0x69, 0x10, 0x2c, 0x36, 0x8b, 0xeb, 0xf8, 0x46, 0x89, 0x1e, 0xf6, 0xb4, 0xdf, 0xb8, 0xfb, 0xc1, 0x59, 0x78, 0xce, 0xf6, 0x83, 0x12, 0x3d, 0xc9, 0x7e, 0x10, 0x6d,
0x7b, 0x66, 0xbb, 0xdb, 0x64, 0xa3, 0xce, 0x04, 0xd0, 0xaf, 0xf8, 0x8b, 0x03, 0x7a, 0xa1, 0xfd, 0x26, 0xd8, 0x0f, 0xa2, 0x77, 0xdf, 0x7e, 0xf0, 0x7d, 0xb6, 0x1f, 0x14, 0xb3, 0xca, 0xf7, 0x83,
0xda, 0xdd, 0x0f, 0x5e, 0x85, 0xe7, 0x6c, 0x3f, 0x28, 0xd1, 0xd3, 0xec, 0x07, 0xd1, 0x66, 0x8a, 0x11, 0x86, 0xbe, 0xc9, 0xdf, 0xcb, 0xbe, 0xbd, 0xde, 0x59, 0xd0, 0x5d, 0x33, 0x6f, 0x7f, 0x37,
0xfd, 0x20, 0x7a, 0xf7, 0xed, 0x07, 0x3f, 0x64, 0xfb, 0x41, 0x31, 0xab, 0x7c, 0x3f, 0x18, 0x61, 0xa7, 0x85, 0xfa, 0x23, 0x58, 0xe7, 0x3b, 0xf6, 0x96, 0x45, 0x1d, 0x39, 0x9f, 0x8f, 0x60, 0xd9,
0xe8, 0x9b, 0xfc, 0xdd, 0xec, 0x9b, 0xcb, 0xed, 0x05, 0xdd, 0x35, 0xf3, 0xf6, 0x77, 0x73, 0x5a, 0xe6, 0x0d, 0xde, 0x74, 0xae, 0x0c, 0xaf, 0x77, 0xf2, 0x02, 0x55, 0x3f, 0x60, 0xdf, 0x79, 0xfe,
0xa8, 0x3f, 0x82, 0x75, 0xbe, 0x63, 0x6f, 0x5b, 0xd4, 0x91, 0xf3, 0xf9, 0x10, 0x96, 0x6d, 0xde, 0xab, 0xad, 0x3d, 0xc7, 0xc3, 0x85, 0x30, 0xc7, 0x50, 0x6a, 0xb0, 0x24, 0x00, 0x18, 0x49, 0x59,
0xe0, 0x4d, 0xe7, 0xca, 0xe8, 0x72, 0x3b, 0x2f, 0x50, 0x8d, 0x7d, 0xf6, 0x9d, 0xe7, 0xbf, 0x3a, 0xbd, 0x67, 0xe0, 0x36, 0x88, 0xd4, 0xfe, 0x9a, 0x82, 0x4d, 0xb9, 0x71, 0x9d, 0x2e, 0x16, 0xb2,
0xda, 0x53, 0x3c, 0x5c, 0x08, 0x73, 0x0c, 0xa5, 0x0e, 0x4b, 0x02, 0x80, 0x91, 0x94, 0xd5, 0x7b, 0x07, 0x45, 0x84, 0x4e, 0x90, 0xd7, 0x55, 0x61, 0x22, 0xd3, 0x5a, 0x0b, 0xa4, 0x75, 0x3b, 0x3a,
0x06, 0x6e, 0x83, 0x48, 0xed, 0x2f, 0x29, 0xb8, 0x21, 0x37, 0xae, 0xb3, 0xc5, 0x42, 0x76, 0xa1, 0x70, 0xdf, 0xf6, 0xe4, 0x23, 0xef, 0x98, 0x32, 0xf3, 0x34, 0xfc, 0x27, 0x0d, 0x44, 0xec, 0xc4,
0x88, 0xd0, 0x29, 0xf2, 0xba, 0x2a, 0x4c, 0x64, 0x5a, 0xeb, 0x81, 0xb4, 0x6e, 0x45, 0x07, 0xee, 0xd8, 0xa3, 0x2b, 0x9b, 0x1f, 0x86, 0x65, 0xb3, 0x1a, 0xbd, 0xe3, 0xf4, 0x1b, 0x8e, 0xaa, 0xe6,
0xdb, 0x9e, 0x7c, 0xe2, 0x1d, 0x53, 0xae, 0x3c, 0x0d, 0xff, 0x4e, 0x03, 0x11, 0x3b, 0x31, 0xf6, 0xe7, 0xf3, 0x57, 0x4d, 0x3d, 0xa4, 0x9a, 0xef, 0x4d, 0x16, 0xdb, 0x8d, 0x88, 0xe6, 0xa1, 0x3c,
0xe8, 0xca, 0xe6, 0xc7, 0x61, 0xd9, 0xac, 0x46, 0xef, 0x38, 0xfd, 0x86, 0xe3, 0xaa, 0xf9, 0xd5, 0x76, 0x60, 0x44, 0x98, 0xb2, 0xef, 0xb2, 0x43, 0x12, 0x6f, 0x42, 0xc9, 0x8c, 0xcb, 0x99, 0x84,
0xfc, 0x55, 0x53, 0x0f, 0xa9, 0xe6, 0x07, 0xd3, 0xc5, 0x76, 0x2d, 0xa2, 0x79, 0x20, 0x8f, 0x1d, 0x6a, 0x75, 0xd8, 0x94, 0x27, 0x76, 0x3f, 0x75, 0x6b, 0x81, 0xbd, 0xee, 0xd8, 0x5c, 0x0a, 0x76,
0x18, 0x11, 0xa6, 0xec, 0x7b, 0xec, 0x90, 0xc4, 0x9b, 0x50, 0x32, 0xe3, 0x72, 0x26, 0xa1, 0x5a, 0x35, 0x03, 0x97, 0x7e, 0x0a, 0x9b, 0xf2, 0xd0, 0x35, 0xe5, 0xea, 0xfe, 0x9a, 0x77, 0xf8, 0xf3,
0x03, 0x6e, 0xc8, 0x13, 0xbb, 0x9f, 0xba, 0xf5, 0xc0, 0x5e, 0x77, 0x62, 0x2e, 0x05, 0xbb, 0xba, 0x47, 0x83, 0xa2, 0xb1, 0x6f, 0xf6, 0xce, 0x8c, 0x73, 0x5f, 0xb7, 0x2d, 0xde, 0x10, 0xea, 0x56,
0x02, 0x97, 0x7e, 0x0a, 0x37, 0xe4, 0xa1, 0x6b, 0xc6, 0xd5, 0xfd, 0x0d, 0xef, 0xf0, 0xe7, 0x8f, 0xa0, 0x58, 0xb7, 0xe2, 0xb5, 0x2b, 0x1a, 0xd2, 0xdc, 0x1b, 0xa1, 0x00, 0xc4, 0x8d, 0x10, 0x6d,
0x06, 0x45, 0x63, 0xcf, 0xec, 0x9f, 0x18, 0xa7, 0xbe, 0x6e, 0xdb, 0xbc, 0x21, 0xd4, 0xad, 0x40, 0x10, 0xe9, 0x13, 0x8d, 0x69, 0x63, 0x61, 0xa2, 0x81, 0xd0, 0x49, 0x44, 0x43, 0x98, 0x4c, 0x20,
0xb1, 0x6e, 0xc5, 0x6b, 0x57, 0x34, 0xa4, 0xb9, 0x37, 0x42, 0x01, 0x88, 0x1b, 0x21, 0xda, 0x20, 0x1a, 0xc2, 0xb3, 0x4a, 0x34, 0xe6, 0x30, 0x0d, 0x52, 0x34, 0x44, 0xf3, 0x14, 0xa2, 0x11, 0x34,
0xd2, 0x27, 0x1a, 0xb3, 0xc6, 0xc2, 0x44, 0x03, 0xa1, 0xd3, 0x88, 0x86, 0x30, 0x99, 0x42, 0x34, 0xfc, 0x6a, 0x89, 0x86, 0x3a, 0xb6, 0x9b, 0x14, 0x0d, 0x37, 0x22, 0x4f, 0x34, 0x44, 0x22, 0x62,
0x84, 0x67, 0x95, 0x68, 0xcc, 0x61, 0x1a, 0xa4, 0x68, 0x88, 0xe6, 0x19, 0x44, 0x23, 0x68, 0xf8, 0x45, 0x03, 0x73, 0x26, 0xa1, 0x9e, 0x68, 0x04, 0xa9, 0x3b, 0x86, 0x68, 0xa8, 0xb8, 0x14, 0xec,
0x76, 0x89, 0x86, 0x3a, 0xb6, 0xeb, 0x14, 0x0d, 0x37, 0x22, 0x4f, 0x34, 0x44, 0x22, 0x62, 0x45, 0x6a, 0x06, 0x2e, 0xb9, 0xa2, 0x31, 0xf5, 0xea, 0x76, 0x45, 0x23, 0x18, 0x4d, 0xed, 0xd7, 0xb7,
0x03, 0x73, 0x26, 0xa1, 0x9e, 0x68, 0x04, 0xa9, 0x3b, 0x81, 0x68, 0xa8, 0xb8, 0x14, 0xec, 0xea, 0x21, 0xb7, 0x2f, 0xee, 0x39, 0x89, 0x01, 0x39, 0xbc, 0x42, 0x24, 0x9a, 0x2a, 0xa8, 0xe0, 0xb5,
0x0a, 0x5c, 0x72, 0x45, 0x63, 0xe6, 0xd5, 0xed, 0x8a, 0x46, 0x30, 0x9a, 0xfa, 0xaf, 0x6e, 0x41, 0x64, 0xf9, 0x5e, 0x2c, 0x06, 0x45, 0xe9, 0xd6, 0xdf, 0xff, 0xfc, 0xff, 0x3f, 0xa4, 0xd7, 0x60,
0x6e, 0x4f, 0x5c, 0xb4, 0x12, 0x03, 0x72, 0x78, 0x85, 0x48, 0x34, 0x55, 0x50, 0xc1, 0x6b, 0xc9, 0x95, 0x83, 0xbe, 0x8d, 0xdb, 0x47, 0x62, 0xc2, 0xb2, 0x7b, 0x07, 0x45, 0xbe, 0x39, 0xce, 0xcd,
0xf2, 0xdd, 0x58, 0x0c, 0x8a, 0xd2, 0xcd, 0xbf, 0xfd, 0xe9, 0x7f, 0xbf, 0x4f, 0xaf, 0xc1, 0x2a, 0x5d, 0xf9, 0x7e, 0x02, 0x2a, 0xde, 0xa1, 0x05, 0xe0, 0x5d, 0x01, 0x91, 0xfb, 0xd1, 0x05, 0x3f,
0x07, 0x7d, 0x07, 0xb7, 0x8f, 0xc4, 0x84, 0x65, 0xf7, 0x0e, 0x8a, 0x7c, 0x7b, 0x92, 0x9b, 0xbb, 0xff, 0x08, 0x1f, 0x24, 0xc1, 0x12, 0x7d, 0x7a, 0x57, 0x3c, 0x6a, 0x9f, 0x23, 0x57, 0x4a, 0x6a,
0xf2, 0xbd, 0x04, 0x54, 0xbc, 0x43, 0x0b, 0xc0, 0xbb, 0x02, 0x22, 0xf7, 0xa2, 0x0b, 0x7e, 0xfe, 0x9f, 0x8a, 0x9b, 0xa2, 0x08, 0x9f, 0x22, 0x87, 0xaf, 0x9b, 0x76, 0x27, 0x32, 0x87, 0xbe, 0x2b,
0x11, 0xde, 0x4f, 0x82, 0x25, 0xfa, 0xf4, 0xae, 0x78, 0xd4, 0x3e, 0xc7, 0xae, 0x94, 0xd4, 0x3e, 0x9e, 0xc8, 0x1c, 0x06, 0x2e, 0x73, 0xe2, 0x73, 0xc8, 0x8b, 0xf4, 0xd1, 0x39, 0xf4, 0x5f, 0x98,
0x15, 0x37, 0x45, 0x11, 0x3e, 0x45, 0x0e, 0x5f, 0xb6, 0xec, 0x6e, 0x64, 0x0e, 0x7d, 0x57, 0x3c, 0x44, 0xe7, 0x30, 0x50, 0xe9, 0x4f, 0x9c, 0x4f, 0x3e, 0xbc, 0x98, 0xf9, 0xf4, 0x8f, 0xf0, 0x41,
0x91, 0x39, 0x0c, 0x5c, 0xe6, 0xc4, 0xe7, 0x90, 0x17, 0xe9, 0xa3, 0x73, 0xe8, 0xbf, 0x30, 0x89, 0x12, 0x2c, 0xd1, 0xa7, 0x57, 0x3b, 0x57, 0xfb, 0x1c, 0xa9, 0xe3, 0xab, 0x7d, 0x8e, 0x96, 0xe0,
0xce, 0x61, 0xa0, 0xd2, 0x9f, 0x38, 0x9f, 0x7c, 0x78, 0x31, 0xf3, 0xe9, 0x1f, 0xe1, 0xfd, 0x24, 0xa3, 0x7c, 0x7e, 0x06, 0x2b, 0xfe, 0xba, 0x1f, 0x79, 0x38, 0x66, 0x21, 0xb3, 0x5c, 0x49, 0x06,
0x58, 0xa2, 0x4f, 0xaf, 0x76, 0xae, 0xf6, 0x39, 0x56, 0xc7, 0x57, 0xfb, 0x1c, 0x2f, 0xc1, 0x47, 0xc6, 0x7b, 0xfe, 0x25, 0xac, 0x06, 0x6e, 0x39, 0x88, 0xb2, 0x47, 0xd5, 0xad, 0x4a, 0xf9, 0xd1,
0xf9, 0xfc, 0x12, 0x56, 0xfc, 0x75, 0x3f, 0xf2, 0x60, 0xc2, 0x42, 0x66, 0xb9, 0x92, 0x0c, 0x8c, 0x18, 0xc8, 0x44, 0xe7, 0x81, 0x22, 0xb9, 0xda, 0xb9, 0xaa, 0x2c, 0xaf, 0x76, 0xae, 0xac, 0xb8,
0xf7, 0xfc, 0x0b, 0x58, 0x0d, 0xdc, 0x72, 0x10, 0x65, 0x8f, 0xaa, 0x5b, 0x95, 0xf2, 0xc3, 0x09, 0xc7, 0x38, 0x0f, 0xd4, 0xc2, 0xd5, 0xce, 0x55, 0x45, 0x77, 0xb5, 0x73, 0x75, 0x61, 0x3d, 0x96,
0x90, 0x89, 0xce, 0x03, 0x45, 0x72, 0xb5, 0x73, 0x55, 0x59, 0x5e, 0xed, 0x5c, 0x59, 0x71, 0x8f, 0x64, 0x58, 0x3f, 0x8a, 0x24, 0x59, 0xb0, 0xe6, 0x18, 0x49, 0xb2, 0x70, 0x01, 0x31, 0x9e, 0x64,
0x71, 0x1e, 0xa8, 0x85, 0xab, 0x9d, 0xab, 0x8a, 0xee, 0x6a, 0xe7, 0xea, 0xc2, 0x7a, 0x2c, 0xc9, 0xb2, 0xd8, 0x15, 0x4d, 0xb2, 0x50, 0x85, 0x2e, 0x9a, 0x64, 0xe1, 0xba, 0x59, 0x22, 0xc9, 0xe4,
0xb0, 0x7e, 0x14, 0x49, 0xb2, 0x60, 0xcd, 0x31, 0x92, 0x64, 0xe1, 0x02, 0x62, 0x3c, 0xc9, 0x64, 0x80, 0x63, 0x48, 0x16, 0x1a, 0xf3, 0xa3, 0x31, 0x90, 0x63, 0xe6, 0x39, 0xd6, 0xb9, 0xaa, 0xc8,
0xb1, 0x2b, 0x9a, 0x64, 0xa1, 0x0a, 0x5d, 0x34, 0xc9, 0xc2, 0x75, 0xb3, 0x44, 0x92, 0xc9, 0x01, 0x1b, 0x97, 0xe7, 0x31, 0x9d, 0x8b, 0x3c, 0xe3, 0x69, 0x3f, 0x32, 0xcf, 0xc1, 0x3a, 0x4a, 0x64,
0xc7, 0x90, 0x2c, 0x34, 0xe6, 0x87, 0x13, 0x20, 0x27, 0xcc, 0x73, 0xac, 0x73, 0x55, 0x91, 0x37, 0x9e, 0x43, 0xa5, 0x86, 0x84, 0x3c, 0xcb, 0x42, 0x54, 0x74, 0x9e, 0x43, 0xd5, 0xb3, 0xe8, 0x3c,
0x2e, 0xcf, 0x13, 0x3a, 0x17, 0x79, 0xc6, 0xd3, 0x7e, 0x64, 0x9e, 0x83, 0x75, 0x94, 0xc8, 0x3c, 0x87, 0x6b, 0x5a, 0x89, 0xeb, 0x59, 0x0e, 0x38, 0x66, 0x3d, 0x87, 0xc6, 0xfc, 0x68, 0x0c, 0x64,
0x87, 0x4a, 0x0d, 0x09, 0x79, 0x96, 0x85, 0xa8, 0xe8, 0x3c, 0x87, 0xaa, 0x67, 0xd1, 0x79, 0x0e, 0xe2, 0xc7, 0xc9, 0x2d, 0x81, 0xa8, 0x3f, 0x4e, 0xe1, 0x02, 0x4b, 0xf9, 0x7e, 0x02, 0x2a, 0x71,
0xd7, 0xb4, 0x12, 0xd7, 0xb3, 0x1c, 0x70, 0xcc, 0x7a, 0x0e, 0x8d, 0xf9, 0xe1, 0x04, 0xc8, 0xc4, 0x9e, 0xfd, 0xf5, 0x06, 0xf5, 0x3c, 0x2b, 0x6a, 0x29, 0xe5, 0x4a, 0x32, 0x30, 0xde, 0xf3, 0x00,
0x8f, 0x93, 0x5b, 0x02, 0x51, 0x7f, 0x9c, 0xc2, 0x05, 0x96, 0xf2, 0xbd, 0x04, 0x54, 0xe2, 0x3c, 0x0a, 0xbe, 0x53, 0x33, 0x79, 0x30, 0xde, 0x41, 0xbf, 0xfc, 0x30, 0x11, 0x97, 0x38, 0x60, 0xff,
0xfb, 0xeb, 0x0d, 0xea, 0x79, 0x56, 0xd4, 0x52, 0xca, 0x95, 0x64, 0x60, 0xbc, 0xe7, 0x21, 0x14, 0xa1, 0x58, 0x3d, 0x60, 0xc5, 0x09, 0xbc, 0x5c, 0x49, 0x06, 0x26, 0x7a, 0xf6, 0x1f, 0x80, 0xd5,
0x7c, 0xa7, 0x66, 0x72, 0x7f, 0xb2, 0x83, 0x7e, 0xf9, 0x41, 0x22, 0x2e, 0x71, 0xc0, 0xfe, 0x43, 0x9e, 0x15, 0x87, 0xec, 0x72, 0x25, 0x19, 0x38, 0x0e, 0xab, 0xc4, 0x16, 0x3a, 0x92, 0x55, 0x81,
0xb1, 0x7a, 0xc0, 0x8a, 0x13, 0x78, 0xb9, 0x92, 0x0c, 0x4c, 0xf4, 0xec, 0x3f, 0x00, 0xab, 0x3d, 0x3d, 0x7a, 0x24, 0xab, 0x82, 0xfb, 0xf0, 0x44, 0x56, 0xa1, 0xcf, 0x18, 0x56, 0x05, 0xdd, 0x56,
0x2b, 0x0e, 0xd9, 0xe5, 0x4a, 0x32, 0x70, 0x12, 0x56, 0x89, 0x2d, 0x74, 0x24, 0xab, 0x02, 0x7b, 0x92, 0x81, 0x63, 0xb1, 0x0a, 0x8f, 0x55, 0xd1, 0xac, 0x0a, 0x9e, 0x04, 0xa3, 0x59, 0x15, 0x3a,
0xf4, 0x48, 0x56, 0x05, 0xf7, 0xe1, 0x89, 0xac, 0x42, 0x9f, 0x31, 0xac, 0x0a, 0xba, 0xad, 0x24, 0x9f, 0x25, 0xb2, 0x2a, 0x6e, 0xc0, 0x8a, 0x23, 0x5a, 0x1c, 0xab, 0xc6, 0x9e, 0x6a, 0xff, 0x09,
0x03, 0x27, 0x62, 0x15, 0x1e, 0xab, 0xa2, 0x59, 0x15, 0x3c, 0x09, 0x46, 0xb3, 0x2a, 0x74, 0x3e, 0x29, 0x8e, 0x55, 0x63, 0x78, 0x56, 0x1d, 0xb6, 0x22, 0x3c, 0xef, 0x95, 0xde, 0x7e, 0xb1, 0xbd,
0x4b, 0x64, 0x55, 0xdc, 0x80, 0x15, 0x47, 0xb4, 0x38, 0x56, 0x4d, 0x3c, 0xd5, 0xfe, 0x13, 0x52, 0xf0, 0xcf, 0x2f, 0xb6, 0x17, 0x7e, 0x35, 0xdc, 0x4e, 0xbd, 0x1d, 0x6e, 0xa7, 0xfe, 0x31, 0xdc,
0x1c, 0xab, 0x26, 0xf0, 0xac, 0x3a, 0x6c, 0x45, 0x78, 0xde, 0x2d, 0xbd, 0xf9, 0x7a, 0x6b, 0xe1, 0x4e, 0xfd, 0x7b, 0xb8, 0x9d, 0x3a, 0x5d, 0xe2, 0xff, 0x12, 0xfa, 0xe4, 0xcb, 0x00, 0x00, 0x00,
0x1f, 0x5f, 0x6f, 0x2d, 0xfc, 0x72, 0xb4, 0x95, 0x7a, 0x33, 0xda, 0x4a, 0xfd, 0x7d, 0xb4, 0x95, 0xff, 0xff, 0x69, 0xfa, 0x48, 0xde, 0x8b, 0x2a, 0x00, 0x00,
0xfa, 0xd7, 0x68, 0x2b, 0x75, 0xbc, 0xc4, 0xff, 0x25, 0xf4, 0xd1, 0xff, 0x03, 0x00, 0x00, 0xff,
0xff, 0x47, 0x18, 0x50, 0x6c, 0x2b, 0x2b, 0x00, 0x00,
} }

View File

@ -2,11 +2,11 @@ syntax = "proto3";
package docker.swarmkit.v1; package docker.swarmkit.v1;
import "github.com/docker/swarmkit/api/specs.proto"; import "specs.proto";
import "github.com/docker/swarmkit/api/objects.proto"; import "objects.proto";
import "github.com/docker/swarmkit/api/types.proto"; import "types.proto";
import "gogoproto/gogo.proto"; import "gogoproto/gogo.proto";
import "github.com/docker/swarmkit/protobuf/plugin/plugin.proto"; import "plugin/plugin.proto";
// Control defines the RPC methods for controlling a cluster. // Control defines the RPC methods for controlling a cluster.
service Control { service Control {

View File

@ -1,5 +1,5 @@
// Code generated by protoc-gen-gogo. // Code generated by protoc-gen-gogo.
// source: github.com/docker/swarmkit/api/dispatcher.proto // source: dispatcher.proto
// DO NOT EDIT! // DO NOT EDIT!
package api package api
@ -1100,7 +1100,7 @@ var _Dispatcher_serviceDesc = grpc.ServiceDesc{
ServerStreams: true, ServerStreams: true,
}, },
}, },
Metadata: "github.com/docker/swarmkit/api/dispatcher.proto", Metadata: "dispatcher.proto",
} }
func (m *SessionRequest) Marshal() (dAtA []byte, err error) { func (m *SessionRequest) Marshal() (dAtA []byte, err error) {
@ -3778,73 +3778,70 @@ var (
ErrIntOverflowDispatcher = fmt.Errorf("proto: integer overflow") ErrIntOverflowDispatcher = fmt.Errorf("proto: integer overflow")
) )
func init() { func init() { proto.RegisterFile("dispatcher.proto", fileDescriptorDispatcher) }
proto.RegisterFile("github.com/docker/swarmkit/api/dispatcher.proto", fileDescriptorDispatcher)
}
var fileDescriptorDispatcher = []byte{ var fileDescriptorDispatcher = []byte{
// 1007 bytes of a gzipped FileDescriptorProto // 983 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x55, 0x4f, 0x6f, 0xe3, 0x44, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x55, 0x4f, 0x6f, 0x1b, 0x45,
0x1c, 0xcd, 0xa4, 0xa9, 0xdb, 0xfc, 0xd2, 0x2d, 0x61, 0xb4, 0x2a, 0xc6, 0xd2, 0xa6, 0xc1, 0x65, 0x14, 0xf7, 0x38, 0xce, 0x26, 0x7e, 0x4e, 0x82, 0x19, 0xaa, 0xb0, 0xac, 0x54, 0xc7, 0x6c, 0x68,
0xab, 0x8a, 0x2d, 0xce, 0x12, 0xfe, 0x1d, 0xa8, 0x0a, 0x4d, 0x13, 0xa9, 0xd1, 0x6e, 0xbb, 0xd5, 0x14, 0xa9, 0x61, 0x53, 0xcc, 0x9f, 0x0b, 0x51, 0x20, 0x8e, 0x2d, 0xc5, 0x6a, 0x93, 0x46, 0x13,
0xb4, 0xbb, 0x7b, 0xac, 0x1c, 0x7b, 0xd6, 0x35, 0x69, 0x3c, 0xc6, 0x33, 0xd9, 0x25, 0x07, 0x24, 0xb7, 0x3d, 0x5a, 0x6b, 0xef, 0x74, 0xb3, 0x38, 0xde, 0x59, 0x76, 0xc6, 0x2d, 0x3e, 0x20, 0x71,
0x0e, 0xac, 0x84, 0x38, 0x21, 0x4e, 0x95, 0x10, 0x5f, 0x01, 0xf1, 0x31, 0x2a, 0x4e, 0x1c, 0x39, 0xa0, 0x12, 0xe2, 0x84, 0x38, 0x45, 0x42, 0x7c, 0x05, 0xc4, 0xc7, 0x88, 0x38, 0x71, 0xe4, 0x14,
0x15, 0x36, 0x1f, 0x80, 0x0f, 0xc0, 0x09, 0x79, 0x3c, 0x4e, 0x42, 0x37, 0x69, 0xd3, 0x9e, 0x12, 0xa8, 0x3f, 0x00, 0x1f, 0x80, 0x13, 0xda, 0xd9, 0x59, 0xdb, 0x75, 0xed, 0xd4, 0xc9, 0xc9, 0x9e,
0xcf, 0xbc, 0xf7, 0xe6, 0xf9, 0xfd, 0x7e, 0xfe, 0x0d, 0x54, 0x3c, 0x5f, 0x1c, 0x77, 0x5b, 0x96, 0x37, 0xbf, 0xdf, 0x9b, 0xdf, 0xbc, 0xf7, 0xdb, 0x37, 0x90, 0x77, 0x3c, 0x1e, 0xd8, 0xa2, 0x75,
0xc3, 0x3a, 0x15, 0x97, 0x39, 0x6d, 0x1a, 0x55, 0xf8, 0x0b, 0x3b, 0xea, 0xb4, 0x7d, 0x51, 0xb1, 0x4a, 0x43, 0x2b, 0x08, 0x99, 0x60, 0x18, 0x3b, 0xac, 0xd5, 0xa6, 0xa1, 0xc5, 0x9f, 0xdb, 0x61,
0x43, 0xbf, 0xe2, 0xfa, 0x3c, 0xb4, 0x85, 0x73, 0x4c, 0x23, 0x2b, 0x8c, 0x98, 0x60, 0x18, 0x27, 0xa7, 0xed, 0x09, 0xeb, 0xd9, 0x47, 0x46, 0x4e, 0xf4, 0x02, 0xca, 0x63, 0x80, 0xb1, 0xcc, 0x9a,
0x28, 0x2b, 0x45, 0x59, 0xcf, 0x3f, 0x30, 0xde, 0xbb, 0x42, 0x44, 0xf4, 0x42, 0xca, 0x13, 0xbe, 0x5f, 0xd1, 0x96, 0x48, 0x96, 0xb7, 0x5c, 0xe6, 0x32, 0xf9, 0x77, 0x3b, 0xfa, 0xa7, 0xa2, 0xef,
0xb1, 0x7e, 0x05, 0x96, 0xb5, 0xbe, 0xa4, 0x8e, 0x48, 0xd1, 0xb7, 0x3d, 0xe6, 0x31, 0xf9, 0xb7, 0x04, 0x67, 0x5d, 0xd7, 0xf3, 0xb7, 0xe3, 0x1f, 0x15, 0x2c, 0xb8, 0x8c, 0xb9, 0x67, 0x74, 0x5b,
0x12, 0xff, 0x53, 0xab, 0x9f, 0x5e, 0xa2, 0x21, 0x11, 0xad, 0xee, 0xb3, 0x4a, 0x78, 0xd2, 0xf5, 0xae, 0x9a, 0xdd, 0xa7, 0xdb, 0x4e, 0x37, 0xb4, 0x85, 0xc7, 0xd4, 0xbe, 0xf9, 0x02, 0xc1, 0xca,
0xfc, 0x40, 0xfd, 0x28, 0x62, 0xc9, 0x63, 0xcc, 0x3b, 0xa1, 0x43, 0x90, 0xdb, 0x8d, 0x6c, 0xe1, 0x09, 0xe5, 0xdc, 0x63, 0x3e, 0xa1, 0x5f, 0x77, 0x29, 0x17, 0xb8, 0x0a, 0x39, 0x87, 0xf2, 0x56,
0x33, 0xb5, 0x6f, 0xbe, 0x44, 0xb0, 0x78, 0x40, 0x39, 0xf7, 0x59, 0x40, 0xe8, 0x57, 0x5d, 0xca, 0xe8, 0x05, 0x11, 0x4e, 0x47, 0x45, 0xb4, 0x99, 0x2b, 0xad, 0x5b, 0xaf, 0x6b, 0xb4, 0x8e, 0x98,
0x05, 0x6e, 0x40, 0xc1, 0xa5, 0xdc, 0x89, 0xfc, 0x30, 0xc6, 0xe9, 0xa8, 0x8c, 0xd6, 0x0a, 0xd5, 0x43, 0x2b, 0x43, 0x28, 0x19, 0xe5, 0xe1, 0x2d, 0x00, 0x1e, 0x27, 0x6e, 0x78, 0x8e, 0x9e, 0x2e,
0x15, 0xeb, 0xf5, 0x14, 0xac, 0x3d, 0xe6, 0xd2, 0xfa, 0x10, 0x4a, 0x46, 0x79, 0x78, 0x1d, 0x80, 0xa2, 0xcd, 0x6c, 0x79, 0xb9, 0x7f, 0xb9, 0x96, 0x55, 0xc7, 0xd5, 0x2a, 0x24, 0xab, 0x00, 0x35,
0x27, 0xc2, 0x47, 0xbe, 0xab, 0x67, 0xcb, 0x68, 0x2d, 0x5f, 0xbb, 0xd5, 0x3f, 0x5f, 0xce, 0xab, 0xc7, 0xfc, 0x25, 0x3d, 0xd0, 0x71, 0x48, 0x39, 0xb7, 0x5d, 0x3a, 0x96, 0x00, 0x5d, 0x9d, 0x00,
0xe3, 0x9a, 0x75, 0x92, 0x57, 0x80, 0xa6, 0x6b, 0xfe, 0x9c, 0x1d, 0xf8, 0xd8, 0xa5, 0x9c, 0xdb, 0x6f, 0x41, 0xc6, 0x67, 0x0e, 0x95, 0x07, 0xe5, 0x4a, 0xfa, 0x34, 0xb9, 0x44, 0xa2, 0xf0, 0x0e,
0x1e, 0xbd, 0x20, 0x80, 0x2e, 0x17, 0xc0, 0xeb, 0x90, 0x0b, 0x98, 0x4b, 0xe5, 0x41, 0x85, 0xaa, 0x2c, 0x76, 0x6c, 0xdf, 0x76, 0x69, 0xc8, 0xf5, 0xb9, 0xe2, 0xdc, 0x66, 0xae, 0x54, 0x9c, 0xc4,
0x3e, 0xc9, 0x2e, 0x91, 0x28, 0xbc, 0x01, 0xf3, 0x1d, 0x3b, 0xb0, 0x3d, 0x1a, 0x71, 0x7d, 0xa6, 0x78, 0x42, 0x3d, 0xf7, 0x54, 0x50, 0xe7, 0x98, 0xd2, 0x90, 0x0c, 0x18, 0xf8, 0x09, 0xac, 0xfa,
0x3c, 0xb3, 0x56, 0xa8, 0x96, 0xc7, 0x31, 0x9e, 0x52, 0xdf, 0x3b, 0x16, 0xd4, 0xdd, 0xa7, 0x34, 0x54, 0x3c, 0x67, 0x61, 0xbb, 0xd1, 0x64, 0x4c, 0x70, 0x11, 0xda, 0x41, 0xa3, 0x4d, 0x7b, 0x5c,
0x22, 0x03, 0x06, 0x7e, 0x0a, 0x4b, 0x01, 0x15, 0x2f, 0x58, 0xd4, 0x3e, 0x6a, 0x31, 0x26, 0xb8, 0xcf, 0xc8, 0x5c, 0xef, 0x4f, 0xca, 0x55, 0xf5, 0x5b, 0x61, 0x4f, 0x96, 0xe6, 0x3e, 0xed, 0x91,
0x88, 0xec, 0xf0, 0xa8, 0x4d, 0x7b, 0x5c, 0xcf, 0x49, 0xad, 0x77, 0xc6, 0x69, 0x35, 0x02, 0x27, 0x5b, 0x2a, 0x41, 0x39, 0xe1, 0xdf, 0xa7, 0x3d, 0x8e, 0x57, 0x41, 0x23, 0x8c, 0x89, 0xfd, 0x3d,
0xea, 0xc9, 0x68, 0x1e, 0xd0, 0x1e, 0xb9, 0xad, 0x04, 0x6a, 0x29, 0xff, 0x01, 0xed, 0x71, 0xbc, 0x7d, 0xbe, 0x88, 0x36, 0x97, 0x88, 0x5a, 0x99, 0x5f, 0x42, 0xfe, 0x80, 0xda, 0xa1, 0x68, 0x52,
0x04, 0x1a, 0x61, 0x4c, 0x6c, 0x6f, 0xe9, 0xb3, 0x65, 0xb4, 0xb6, 0x40, 0xd4, 0x93, 0xf9, 0x05, 0x5b, 0x24, 0x6d, 0xba, 0x56, 0x79, 0xcc, 0x63, 0x78, 0x7b, 0x24, 0x03, 0x0f, 0x98, 0xcf, 0x29,
0x14, 0x77, 0xa8, 0x1d, 0x89, 0x16, 0xb5, 0x45, 0x5a, 0xa6, 0x6b, 0xc5, 0x63, 0xee, 0xc3, 0x9b, 0xfe, 0x1c, 0xb4, 0x80, 0x86, 0x1e, 0x73, 0x54, 0x93, 0xdf, 0xb3, 0x62, 0xb7, 0x58, 0x89, 0x5b,
0x23, 0x0a, 0x3c, 0x64, 0x01, 0xa7, 0xf8, 0x33, 0xd0, 0x42, 0x1a, 0xf9, 0xcc, 0x55, 0x45, 0x7e, 0xac, 0x8a, 0x72, 0x4b, 0x79, 0xf1, 0xe2, 0x72, 0x2d, 0x75, 0xfe, 0xf7, 0x1a, 0x22, 0x8a, 0x62,
0xdb, 0x4a, 0xba, 0xc5, 0x4a, 0xbb, 0xc5, 0xaa, 0xab, 0x6e, 0xa9, 0xcd, 0x9f, 0x9d, 0x2f, 0x67, 0xfe, 0x94, 0x86, 0x77, 0x1f, 0x05, 0x8e, 0x2d, 0x68, 0xdd, 0xe6, 0xed, 0x13, 0x61, 0x8b, 0x2e,
0x4e, 0xff, 0x5a, 0x46, 0x44, 0x51, 0xcc, 0x1f, 0xb3, 0xf0, 0xd6, 0xe3, 0xd0, 0xb5, 0x05, 0x3d, 0xbf, 0x91, 0x36, 0xfc, 0x18, 0x16, 0xba, 0x32, 0x51, 0xd2, 0x8b, 0x9d, 0x49, 0xf5, 0x9b, 0x72,
0xb4, 0x79, 0xfb, 0x40, 0xd8, 0xa2, 0xcb, 0x6f, 0xe4, 0x0d, 0x3f, 0x81, 0xb9, 0xae, 0x14, 0x4a, 0x96, 0x35, 0x8c, 0xc4, 0x08, 0x92, 0x24, 0x33, 0x18, 0xe4, 0xc7, 0x37, 0xf1, 0x3a, 0x2c, 0x08,
0x6b, 0xb1, 0x31, 0x2e, 0xbf, 0x09, 0x67, 0x59, 0xc3, 0x95, 0x04, 0x41, 0x52, 0x31, 0x83, 0x41, 0x9b, 0xb7, 0x87, 0xb2, 0xa0, 0x7f, 0xb9, 0xa6, 0x45, 0xb0, 0x5a, 0x85, 0x68, 0xd1, 0x56, 0xcd,
0xf1, 0xe2, 0x26, 0x5e, 0x81, 0x39, 0x61, 0xf3, 0xf6, 0xd0, 0x16, 0xf4, 0xcf, 0x97, 0xb5, 0x18, 0xc1, 0x9f, 0x81, 0xc6, 0x25, 0x49, 0xb9, 0xa9, 0x30, 0x49, 0xcf, 0x88, 0x12, 0x85, 0x36, 0x0d,
0xd6, 0xac, 0x13, 0x2d, 0xde, 0x6a, 0xba, 0xf8, 0x13, 0xd0, 0xb8, 0x24, 0xa9, 0x6e, 0x2a, 0x8d, 0xd0, 0x5f, 0x57, 0x19, 0xd7, 0xda, 0xdc, 0x81, 0xa5, 0x28, 0x7a, 0xb3, 0x12, 0x99, 0xbb, 0x8a,
0xf3, 0x33, 0xe2, 0x44, 0xa1, 0x4d, 0x03, 0xf4, 0xd7, 0x5d, 0x26, 0x59, 0x9b, 0x1b, 0xb0, 0x10, 0x9d, 0x7c, 0x1b, 0x16, 0xcc, 0x47, 0x5a, 0xb9, 0x8e, 0x64, 0xc1, 0xf4, 0x69, 0x02, 0x49, 0x0c,
0xaf, 0xde, 0x2c, 0x22, 0x73, 0x53, 0xb1, 0xd3, 0x6f, 0xc3, 0x82, 0xd9, 0xd8, 0x2b, 0xd7, 0x91, 0x33, 0xcb, 0x80, 0xf7, 0x38, 0xf7, 0x5c, 0xbf, 0x43, 0x7d, 0x71, 0x43, 0x0d, 0xbf, 0x23, 0x80,
0x0c, 0x4c, 0x9f, 0x64, 0x90, 0x24, 0x30, 0xb3, 0x06, 0x78, 0x8b, 0x73, 0xdf, 0x0b, 0x3a, 0x34, 0x61, 0x12, 0x6c, 0x41, 0x26, 0xca, 0xad, 0xac, 0x33, 0x55, 0xc1, 0x41, 0x8a, 0x48, 0x1c, 0xfe,
0x10, 0x37, 0xf4, 0xf0, 0x1b, 0x02, 0x18, 0x8a, 0x60, 0x0b, 0x72, 0xb1, 0xb6, 0x6a, 0x9d, 0x89, 0x04, 0x34, 0x4e, 0x5b, 0x21, 0x15, 0xaa, 0xa8, 0xc6, 0x24, 0xc6, 0x89, 0x44, 0x1c, 0xa4, 0x88,
0x0e, 0x76, 0x32, 0x44, 0xe2, 0xf0, 0x47, 0xa0, 0x71, 0xea, 0x44, 0x54, 0xa8, 0x50, 0x8d, 0x71, 0xc2, 0x46, 0xac, 0x16, 0xf3, 0x9f, 0x7a, 0xae, 0x3e, 0x37, 0x9d, 0xb5, 0x2f, 0x11, 0x11, 0x2b,
0x8c, 0x03, 0x89, 0xd8, 0xc9, 0x10, 0x85, 0x8d, 0x59, 0x0e, 0x0b, 0x9e, 0xf9, 0x9e, 0x3e, 0x33, 0xc6, 0x96, 0x35, 0xc8, 0x78, 0x82, 0x76, 0xcc, 0x17, 0x69, 0xc8, 0x0f, 0x25, 0xef, 0x9f, 0xda,
0x99, 0xb5, 0x2d, 0x11, 0x31, 0x2b, 0xc1, 0xd6, 0x34, 0xc8, 0xf9, 0x82, 0x76, 0xcc, 0x97, 0x59, 0xbe, 0x4b, 0xf1, 0x2e, 0x80, 0x3d, 0x88, 0x29, 0xf9, 0x13, 0x3b, 0x3c, 0x64, 0x92, 0x11, 0x06,
0x28, 0x0e, 0x2d, 0x6f, 0x1f, 0xdb, 0x81, 0x47, 0xf1, 0x26, 0x80, 0x3d, 0x58, 0x53, 0xf6, 0xc7, 0x3e, 0x04, 0xcd, 0x6e, 0xc9, 0xd1, 0x18, 0x5d, 0x64, 0xa5, 0xf4, 0xe9, 0xd5, 0xdc, 0xf8, 0xd4,
0x56, 0x78, 0xc8, 0x24, 0x23, 0x0c, 0xbc, 0x0b, 0x9a, 0xed, 0xc8, 0xd1, 0x18, 0xbf, 0xc8, 0x62, 0x91, 0xc0, 0x9e, 0x24, 0x13, 0x95, 0xc4, 0x6c, 0x8e, 0x4a, 0x8c, 0xf7, 0xf0, 0x06, 0x68, 0x8f,
0xf5, 0xe3, 0xcb, 0xb9, 0xc9, 0xa9, 0x23, 0x0b, 0x5b, 0x92, 0x4c, 0x94, 0x88, 0xd9, 0x1a, 0xb5, 0x8e, 0x2b, 0x7b, 0xf5, 0x6a, 0x3e, 0x65, 0x18, 0x3f, 0xfe, 0x5a, 0x5c, 0x1d, 0x47, 0x28, 0x37,
0x98, 0xec, 0xe1, 0x55, 0xd0, 0x1e, 0xef, 0xd7, 0xb7, 0x0e, 0x1b, 0xc5, 0x8c, 0x61, 0xfc, 0xf0, 0x6f, 0x80, 0x46, 0xaa, 0x87, 0x0f, 0x1f, 0x57, 0xf3, 0x68, 0x32, 0x8e, 0xd0, 0x0e, 0x7b, 0x46,
0x4b, 0x79, 0xe9, 0x22, 0x42, 0x75, 0xf3, 0x2a, 0x68, 0xa4, 0xb1, 0xfb, 0xe8, 0x49, 0xa3, 0x88, 0xcd, 0xff, 0xd0, 0x2b, 0xfd, 0x4f, 0x5c, 0xf4, 0x05, 0x64, 0xa2, 0x57, 0x46, 0xd6, 0x60, 0xa5,
0xc6, 0xe3, 0x08, 0xed, 0xb0, 0xe7, 0xd4, 0xfc, 0x17, 0xfd, 0xaf, 0xfe, 0x69, 0x17, 0x7d, 0x0e, 0x74, 0xf7, 0xea, 0x7b, 0x24, 0x2c, 0xab, 0xde, 0x0b, 0x28, 0x91, 0x44, 0x7c, 0x1b, 0xc0, 0x0e,
0xb9, 0xf8, 0xa2, 0x92, 0x19, 0x2c, 0x56, 0xef, 0x5d, 0xfe, 0x1e, 0x29, 0xcb, 0x3a, 0xec, 0x85, 0x82, 0x33, 0x8f, 0xf2, 0x86, 0x60, 0xf1, 0x8c, 0x27, 0x59, 0x15, 0xa9, 0xb3, 0x68, 0x3b, 0xa4,
0x94, 0x48, 0x22, 0xbe, 0x03, 0x60, 0x87, 0xe1, 0x89, 0x4f, 0xf9, 0x91, 0x60, 0xc9, 0x8c, 0x27, 0xbc, 0x7b, 0x26, 0x78, 0xc3, 0xf3, 0x65, 0x03, 0xb3, 0x24, 0xab, 0x22, 0x35, 0x1f, 0xef, 0xc2,
0x79, 0xb5, 0x72, 0xc8, 0xe2, 0xed, 0x88, 0xf2, 0xee, 0x89, 0xe0, 0x47, 0x7e, 0x20, 0x0b, 0x98, 0x42, 0x4b, 0x16, 0x27, 0x99, 0x9b, 0x1f, 0xcc, 0x52, 0x49, 0x92, 0x90, 0xcc, 0x3b, 0x90, 0x89,
0x27, 0x79, 0xb5, 0xd2, 0x0c, 0xf0, 0x26, 0xcc, 0x39, 0x32, 0x9c, 0x74, 0x6e, 0xbe, 0x3b, 0x4d, 0xb4, 0xe0, 0x25, 0x58, 0xdc, 0x7f, 0x78, 0x78, 0xfc, 0xa0, 0x1a, 0xd5, 0x0b, 0xbf, 0x05, 0xb9,
0x92, 0x24, 0x25, 0x99, 0x77, 0x21, 0x17, 0x7b, 0xc1, 0x0b, 0x30, 0xbf, 0xfd, 0x68, 0x77, 0xff, 0xda, 0xd1, 0x3e, 0xa9, 0x1e, 0x56, 0x8f, 0xea, 0x7b, 0x0f, 0xf2, 0xa8, 0x74, 0x3e, 0x0f, 0x50,
0x61, 0x23, 0xce, 0x0b, 0xbf, 0x01, 0x85, 0xe6, 0xde, 0x36, 0x69, 0xec, 0x36, 0xf6, 0x0e, 0xb7, 0x19, 0x3c, 0xb9, 0xf8, 0x1b, 0x58, 0x50, 0xf6, 0xc6, 0xe6, 0x64, 0x0b, 0x8e, 0xbe, 0x86, 0xc6,
0x1e, 0x16, 0x51, 0xf5, 0x74, 0x16, 0xa0, 0x3e, 0xb8, 0xd4, 0xf1, 0xd7, 0x30, 0xa7, 0xda, 0x1b, 0x55, 0x18, 0x55, 0x11, 0x73, 0xfd, 0x8f, 0xdf, 0xfe, 0x3d, 0x4f, 0xdf, 0x86, 0x25, 0x89, 0xf9,
0x9b, 0xe3, 0x5b, 0x70, 0xf4, 0x36, 0x34, 0x2e, 0xc3, 0xa8, 0x44, 0xcc, 0x95, 0xdf, 0x7f, 0xfd, 0x30, 0x9a, 0xeb, 0x34, 0x84, 0xe5, 0x78, 0xa5, 0x5e, 0x8d, 0x7b, 0x08, 0x7f, 0x0b, 0xd9, 0xc1,
0xe7, 0x34, 0x7b, 0x07, 0x16, 0x24, 0xe6, 0xfd, 0x78, 0xae, 0xd3, 0x08, 0x6e, 0x25, 0x4f, 0xea, 0x0c, 0xc6, 0x13, 0xef, 0x3a, 0x3e, 0xe4, 0x8d, 0x3b, 0x6f, 0x40, 0xa9, 0xe1, 0x32, 0x8b, 0x00,
0xd6, 0xb8, 0x8f, 0xf0, 0x37, 0x90, 0x1f, 0xcc, 0x60, 0x3c, 0xf6, 0x5d, 0x2f, 0x0e, 0x79, 0xe3, 0xfc, 0x33, 0x82, 0xfc, 0xf8, 0x78, 0xc2, 0x77, 0xaf, 0x31, 0x6a, 0x8d, 0xad, 0xd9, 0xc0, 0xd7,
0xee, 0x15, 0x28, 0x35, 0x5c, 0xa6, 0x31, 0x80, 0x7f, 0x42, 0x50, 0xbc, 0x38, 0x9e, 0xf0, 0xbd, 0x11, 0xd5, 0x85, 0x79, 0x39, 0xd8, 0x70, 0x71, 0xda, 0x00, 0x19, 0x9c, 0x3e, 0x1d, 0x91, 0xf4,
0x6b, 0x8c, 0x5a, 0x63, 0x7d, 0x3a, 0xf0, 0x75, 0x4c, 0x75, 0x61, 0x56, 0x0e, 0x36, 0x5c, 0x9e, 0x61, 0x63, 0x86, 0x13, 0x7f, 0x48, 0xa3, 0x7b, 0x08, 0x7f, 0x8f, 0x20, 0x37, 0x62, 0x6d, 0xbc,
0x34, 0x40, 0x06, 0xa7, 0x4f, 0x46, 0xa4, 0x75, 0x58, 0x9d, 0xe2, 0xc4, 0xef, 0xb3, 0xe8, 0x3e, 0xf1, 0x06, 0xef, 0x27, 0x1a, 0x36, 0x66, 0xfb, 0x46, 0x66, 0x74, 0x44, 0x59, 0xbf, 0x78, 0x59,
0xc2, 0xdf, 0x21, 0x28, 0x8c, 0xb4, 0x36, 0x5e, 0xbd, 0xa2, 0xf7, 0x53, 0x0f, 0xab, 0xd3, 0x7d, 0x48, 0xfd, 0xf5, 0xb2, 0x90, 0xfa, 0xae, 0x5f, 0x40, 0x17, 0xfd, 0x02, 0xfa, 0xb3, 0x5f, 0x40,
0x23, 0x53, 0x76, 0x44, 0x4d, 0x3f, 0x7b, 0x55, 0xca, 0xfc, 0xf9, 0xaa, 0x94, 0xf9, 0xb6, 0x5f, 0xff, 0xf4, 0x0b, 0xa8, 0xa9, 0xc9, 0x27, 0xf8, 0xe3, 0xff, 0x03, 0x00, 0x00, 0xff, 0xff, 0x6c,
0x42, 0x67, 0xfd, 0x12, 0xfa, 0xa3, 0x5f, 0x42, 0x7f, 0xf7, 0x4b, 0xa8, 0xa5, 0xc9, 0x2b, 0xf8, 0xba, 0x38, 0xbd, 0x2d, 0x0a, 0x00, 0x00,
0xc3, 0xff, 0x02, 0x00, 0x00, 0xff, 0xff, 0xe0, 0xf0, 0x6a, 0xcb, 0xae, 0x0a, 0x00, 0x00,
} }

View File

@ -2,10 +2,10 @@ syntax = "proto3";
package docker.swarmkit.v1; package docker.swarmkit.v1;
import "github.com/docker/swarmkit/api/types.proto"; import "types.proto";
import "github.com/docker/swarmkit/api/objects.proto"; import "objects.proto";
import "gogoproto/gogo.proto"; import "gogoproto/gogo.proto";
import "github.com/docker/swarmkit/protobuf/plugin/plugin.proto"; import "plugin/plugin.proto";
import "google/protobuf/duration.proto"; import "google/protobuf/duration.proto";
// Dispatcher is the API provided by a manager group for agents to connect to. Agents // Dispatcher is the API provided by a manager group for agents to connect to. Agents

3
vendor/github.com/docker/swarmkit/api/gen.go generated vendored Normal file
View File

@ -0,0 +1,3 @@
package api
//go:generate protoc -I.:../protobuf:../vendor:../vendor/github.com/gogo/protobuf --gogoswarm_out=plugins=grpc+deepcopy+storeobject+raftproxy+authenticatedwrapper,import_path=github.com/docker/swarmkit/api,Mgogoproto/gogo.proto=github.com/gogo/protobuf/gogoproto,Mgoogle/protobuf/descriptor.proto=github.com/gogo/protobuf/protoc-gen-gogo/descriptor,Mplugin/plugin.proto=github.com/docker/swarmkit/protobuf/plugin,Mgoogle/protobuf/duration.proto=github.com/gogo/protobuf/types,Mgoogle/protobuf/timestamp.proto=github.com/gogo/protobuf/types,Mgoogle/protobuf/any.proto=github.com/gogo/protobuf/types:. types.proto specs.proto objects.proto control.proto dispatcher.proto ca.proto snapshot.proto raft.proto health.proto resource.proto logbroker.proto watch.proto

View File

@ -1,5 +1,5 @@
// Code generated by protoc-gen-gogo. // Code generated by protoc-gen-gogo.
// source: github.com/docker/swarmkit/api/health.proto // source: health.proto
// DO NOT EDIT! // DO NOT EDIT!
package api package api
@ -198,7 +198,7 @@ var _Health_serviceDesc = grpc.ServiceDesc{
}, },
}, },
Streams: []grpc.StreamDesc{}, Streams: []grpc.StreamDesc{},
Metadata: "github.com/docker/swarmkit/api/health.proto", Metadata: "health.proto",
} }
func (m *HealthCheckRequest) Marshal() (dAtA []byte, err error) { func (m *HealthCheckRequest) Marshal() (dAtA []byte, err error) {
@ -696,28 +696,26 @@ var (
ErrIntOverflowHealth = fmt.Errorf("proto: integer overflow") ErrIntOverflowHealth = fmt.Errorf("proto: integer overflow")
) )
func init() { proto.RegisterFile("github.com/docker/swarmkit/api/health.proto", fileDescriptorHealth) } func init() { proto.RegisterFile("health.proto", fileDescriptorHealth) }
var fileDescriptorHealth = []byte{ var fileDescriptorHealth = []byte{
// 315 bytes of a gzipped FileDescriptorProto // 287 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xd2, 0x4e, 0xcf, 0x2c, 0xc9, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0xc9, 0x48, 0x4d, 0xcc,
0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x4f, 0xc9, 0x4f, 0xce, 0x4e, 0x2d, 0xd2, 0x2f, 0x2e, 0x29, 0xc9, 0xd0, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x4a, 0xc9, 0x4f, 0xce, 0x4e, 0x2d,
0x4f, 0x2c, 0xca, 0xcd, 0xce, 0x2c, 0xd1, 0x4f, 0x2c, 0xc8, 0xd4, 0xcf, 0x48, 0x4d, 0xcc, 0x29, 0xd2, 0x2b, 0x2e, 0x4f, 0x2c, 0xca, 0xcd, 0xce, 0x2c, 0xd1, 0x2b, 0x33, 0x94, 0x12, 0x49, 0xcf,
0xc9, 0xd0, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x82, 0xa8, 0xd0, 0x83, 0xa9, 0xd0, 0x2b, 0x4f, 0xcf, 0x07, 0x4b, 0xeb, 0x83, 0x58, 0x10, 0x95, 0x52, 0xc2, 0x05, 0x39, 0xa5, 0xe9, 0x99,
0x33, 0x94, 0x12, 0x49, 0xcf, 0x4f, 0xcf, 0x07, 0x4b, 0xeb, 0x83, 0x58, 0x10, 0x95, 0x52, 0xe6, 0x79, 0xfa, 0x10, 0x0a, 0x22, 0xa8, 0xa4, 0xc7, 0x25, 0xe4, 0x01, 0x36, 0xce, 0x39, 0x23, 0x35,
0x78, 0x8c, 0x05, 0xab, 0x48, 0x2a, 0x4d, 0xd3, 0x2f, 0xc8, 0x29, 0x4d, 0xcf, 0xcc, 0x83, 0x52, 0x39, 0x3b, 0x28, 0xb5, 0xb0, 0x34, 0xb5, 0xb8, 0x44, 0x48, 0x82, 0x8b, 0xbd, 0x38, 0xb5, 0xa8,
0x10, 0x8d, 0x4a, 0x7a, 0x5c, 0x42, 0x1e, 0x60, 0x2b, 0x9d, 0x33, 0x52, 0x93, 0xb3, 0x83, 0x52, 0x2c, 0x33, 0x39, 0x55, 0x82, 0x51, 0x81, 0x51, 0x83, 0x33, 0x08, 0xc6, 0x55, 0x5a, 0xc0, 0xc8,
0x0b, 0x4b, 0x53, 0x8b, 0x4b, 0x84, 0x24, 0xb8, 0xd8, 0x8b, 0x53, 0x8b, 0xca, 0x32, 0x93, 0x53, 0x25, 0x8c, 0xa2, 0xa1, 0xb8, 0x20, 0x3f, 0xaf, 0x38, 0x55, 0xc8, 0x97, 0x8b, 0xad, 0xb8, 0x24,
0x25, 0x18, 0x15, 0x18, 0x35, 0x38, 0x83, 0x60, 0x5c, 0xa5, 0x05, 0x8c, 0x5c, 0xc2, 0x28, 0x1a, 0xb1, 0xa4, 0xb4, 0x18, 0xac, 0x81, 0xcf, 0xc8, 0x54, 0x0f, 0xd3, 0x5d, 0x7a, 0x58, 0x34, 0xea,
0x8a, 0x0b, 0xf2, 0xf3, 0x8a, 0x53, 0x85, 0x7c, 0xb9, 0xd8, 0x8a, 0x4b, 0x12, 0x4b, 0x4a, 0x8b, 0x05, 0x83, 0x0c, 0xce, 0x4b, 0x0f, 0x06, 0x6b, 0x0e, 0x82, 0x1a, 0xa2, 0x64, 0xc5, 0xc5, 0x8b,
0xc1, 0x1a, 0xf8, 0x8c, 0x4c, 0xf5, 0x30, 0xdd, 0xae, 0x87, 0x45, 0xa3, 0x5e, 0x30, 0xc8, 0xe0, 0x22, 0x21, 0xc4, 0xcd, 0xc5, 0x1e, 0xea, 0xe7, 0xed, 0xe7, 0x1f, 0xee, 0x27, 0xc0, 0x00, 0xe2,
0xbc, 0xf4, 0x60, 0xb0, 0xe6, 0x20, 0xa8, 0x21, 0x4a, 0x56, 0x5c, 0xbc, 0x28, 0x12, 0x42, 0xdc, 0x04, 0xbb, 0x06, 0x85, 0x79, 0xfa, 0xb9, 0x0b, 0x30, 0x0a, 0xf1, 0x73, 0x71, 0xfb, 0xf9, 0x87,
0x5c, 0xec, 0xa1, 0x7e, 0xde, 0x7e, 0xfe, 0xe1, 0x7e, 0x02, 0x0c, 0x20, 0x4e, 0xb0, 0x6b, 0x50, 0xc4, 0xc3, 0x04, 0x98, 0x8c, 0x2a, 0xb9, 0xd8, 0x20, 0x16, 0x09, 0xe5, 0x73, 0xb1, 0x82, 0x2d,
0x98, 0xa7, 0x9f, 0xbb, 0x00, 0xa3, 0x10, 0x3f, 0x17, 0xb7, 0x9f, 0x7f, 0x48, 0x3c, 0x4c, 0x80, 0x13, 0x52, 0x23, 0xe8, 0x1a, 0xb0, 0xbf, 0xa5, 0xd4, 0x89, 0x74, 0xb5, 0x92, 0xe8, 0xa9, 0x75,
0xc9, 0xa8, 0x92, 0x8b, 0x0d, 0x62, 0x91, 0x50, 0x3e, 0x17, 0x2b, 0xd8, 0x32, 0x21, 0x35, 0x82, 0xef, 0x66, 0x30, 0xf1, 0x73, 0xf1, 0x82, 0x15, 0xea, 0xe6, 0x26, 0xe6, 0x25, 0xa6, 0xa7, 0x16,
0xae, 0x01, 0xfb, 0x5b, 0x4a, 0x9d, 0x48, 0x57, 0x2b, 0x89, 0x9e, 0x5a, 0xf7, 0x6e, 0x06, 0x13, 0x39, 0x49, 0x9c, 0x78, 0x28, 0xc7, 0x70, 0xe3, 0xa1, 0x1c, 0x43, 0xc3, 0x23, 0x39, 0xc6, 0x13,
0x3f, 0x17, 0x2f, 0x58, 0xa1, 0x6e, 0x6e, 0x62, 0x5e, 0x62, 0x7a, 0x6a, 0x91, 0x93, 0xc4, 0x89, 0x8f, 0xe4, 0x18, 0x2f, 0x3c, 0x92, 0x63, 0x7c, 0xf0, 0x48, 0x8e, 0x31, 0x89, 0x0d, 0x1c, 0xdc,
0x87, 0x72, 0x0c, 0x37, 0x1e, 0xca, 0x31, 0x34, 0x3c, 0x92, 0x63, 0x3c, 0xf1, 0x48, 0x8e, 0xf1, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x59, 0xcd, 0x52, 0xee, 0xbd, 0x01, 0x00, 0x00,
0xc2, 0x23, 0x39, 0xc6, 0x07, 0x8f, 0xe4, 0x18, 0x93, 0xd8, 0xc0, 0xc1, 0x6d, 0x0c, 0x08, 0x00,
0x00, 0xff, 0xff, 0x7b, 0xf2, 0xdd, 0x23, 0x00, 0x02, 0x00, 0x00,
} }

View File

@ -12,7 +12,7 @@ syntax = "proto3";
package docker.swarmkit.v1; package docker.swarmkit.v1;
import "gogoproto/gogo.proto"; import "gogoproto/gogo.proto";
import "github.com/docker/swarmkit/protobuf/plugin/plugin.proto"; import "plugin/plugin.proto";
service Health { service Health {
rpc Check(HealthCheckRequest) returns (HealthCheckResponse) { rpc Check(HealthCheckRequest) returns (HealthCheckResponse) {

View File

@ -1,5 +1,5 @@
// Code generated by protoc-gen-gogo. // Code generated by protoc-gen-gogo.
// source: github.com/docker/swarmkit/api/logbroker.proto // source: logbroker.proto
// DO NOT EDIT! // DO NOT EDIT!
package api package api
@ -618,7 +618,7 @@ var _Logs_serviceDesc = grpc.ServiceDesc{
ServerStreams: true, ServerStreams: true,
}, },
}, },
Metadata: "github.com/docker/swarmkit/api/logbroker.proto", Metadata: "logbroker.proto",
} }
// Client API for LogBroker service // Client API for LogBroker service
@ -790,7 +790,7 @@ var _LogBroker_serviceDesc = grpc.ServiceDesc{
ClientStreams: true, ClientStreams: true,
}, },
}, },
Metadata: "github.com/docker/swarmkit/api/logbroker.proto", Metadata: "logbroker.proto",
} }
func (m *LogSubscriptionOptions) Marshal() (dAtA []byte, err error) { func (m *LogSubscriptionOptions) Marshal() (dAtA []byte, err error) {
@ -3350,71 +3350,67 @@ var (
ErrIntOverflowLogbroker = fmt.Errorf("proto: integer overflow") ErrIntOverflowLogbroker = fmt.Errorf("proto: integer overflow")
) )
func init() { func init() { proto.RegisterFile("logbroker.proto", fileDescriptorLogbroker) }
proto.RegisterFile("github.com/docker/swarmkit/api/logbroker.proto", fileDescriptorLogbroker)
}
var fileDescriptorLogbroker = []byte{ var fileDescriptorLogbroker = []byte{
// 966 bytes of a gzipped FileDescriptorProto // 944 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x95, 0x41, 0x6f, 0x1b, 0x45, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x95, 0x41, 0x6f, 0x1b, 0x45,
0x14, 0xc7, 0x3d, 0xeb, 0xc4, 0x8e, 0x9f, 0x9b, 0xc4, 0x9d, 0xa4, 0x91, 0x65, 0xa8, 0x6d, 0x6d, 0x14, 0xc7, 0x3d, 0xeb, 0xc4, 0x8e, 0x9f, 0x9b, 0xc4, 0x1d, 0xa7, 0x91, 0x65, 0xa8, 0x6d, 0x6d,
0xa5, 0x62, 0x45, 0x65, 0xdd, 0x1a, 0xa1, 0x22, 0x45, 0x42, 0xd4, 0xb8, 0x42, 0x16, 0x6e, 0x82, 0xa5, 0x62, 0x45, 0xc5, 0x6e, 0x8d, 0x10, 0x48, 0x91, 0x10, 0x35, 0xae, 0x90, 0x85, 0x9b, 0xa0,
0xc6, 0x8e, 0xe0, 0x16, 0xad, 0xbd, 0xd3, 0xed, 0xca, 0xeb, 0x1d, 0xb3, 0x33, 0x4e, 0x40, 0xe2, 0xb1, 0x23, 0xb8, 0x45, 0x6b, 0xef, 0x74, 0x59, 0x79, 0xbd, 0x63, 0x76, 0xc6, 0x09, 0x48, 0x1c,
0xc0, 0xa1, 0x48, 0x28, 0x07, 0x6e, 0x48, 0x70, 0xe8, 0x89, 0x5e, 0x10, 0x12, 0x17, 0x6e, 0x7c, 0x38, 0x14, 0x09, 0xe5, 0xc0, 0x0d, 0x09, 0x0e, 0x3d, 0xd1, 0x0b, 0x42, 0xe2, 0xc2, 0x8d, 0x0f,
0x00, 0x14, 0x71, 0xe2, 0xc8, 0xc9, 0xa2, 0xfb, 0x01, 0xf8, 0x0c, 0x68, 0x67, 0xd6, 0xeb, 0x0d, 0x80, 0x22, 0x4e, 0x1c, 0x39, 0x59, 0x74, 0x3f, 0x00, 0x9f, 0x01, 0xed, 0xcc, 0xd8, 0xde, 0x60,
0xb6, 0x53, 0x54, 0x2e, 0xf6, 0x8c, 0xe7, 0xf7, 0xf6, 0xfd, 0xdf, 0x7f, 0xde, 0x5b, 0x83, 0x61, 0xbb, 0x45, 0xe5, 0x92, 0xcc, 0xec, 0xfc, 0xdf, 0xbe, 0xdf, 0xfb, 0xcf, 0x7b, 0x6b, 0xd8, 0xf5,
0x3b, 0xe2, 0xc9, 0xa4, 0x6f, 0x0c, 0xd8, 0xa8, 0x6e, 0xb1, 0xc1, 0x90, 0xfa, 0x75, 0x7e, 0x66, 0x98, 0xd3, 0x0f, 0xd8, 0x90, 0x06, 0xb5, 0x71, 0xc0, 0x04, 0xc3, 0xd8, 0x66, 0x83, 0x68, 0xc7,
0xfa, 0xa3, 0xa1, 0x23, 0xea, 0xe6, 0xd8, 0xa9, 0xbb, 0xcc, 0xee, 0xfb, 0x6c, 0x48, 0x7d, 0x63, 0xcf, 0xad, 0x60, 0x34, 0x74, 0x45, 0xed, 0xec, 0x5e, 0x71, 0xcf, 0x61, 0x0e, 0x93, 0xc7, 0xf5,
0xec, 0x33, 0xc1, 0x30, 0x56, 0x90, 0x31, 0x83, 0x8c, 0xd3, 0x7b, 0xa5, 0x5d, 0x9b, 0xd9, 0x4c, 0x68, 0xa5, 0x94, 0xc5, 0xb2, 0xc3, 0x98, 0xe3, 0xd1, 0xba, 0xdc, 0xf5, 0x27, 0x8f, 0xea, 0xc2,
0x1e, 0xd7, 0xc3, 0x95, 0x22, 0x4b, 0x15, 0x9b, 0x31, 0xdb, 0xa5, 0x75, 0xb9, 0xeb, 0x4f, 0x1e, 0x1d, 0x51, 0x2e, 0xac, 0xd1, 0x58, 0x0b, 0xf2, 0x63, 0x6f, 0xe2, 0xb8, 0x7e, 0x5d, 0xfd, 0x53,
0xd7, 0x85, 0x33, 0xa2, 0x5c, 0x98, 0xa3, 0x71, 0x04, 0xdc, 0xbf, 0x22, 0x75, 0x1c, 0x34, 0x76, 0x0f, 0xcd, 0x5f, 0x10, 0xec, 0x77, 0x98, 0xd3, 0x9d, 0xf4, 0xf9, 0x20, 0x70, 0xc7, 0xc2, 0x65,
0x27, 0xb6, 0xe3, 0x45, 0x5f, 0x2a, 0x50, 0xff, 0x05, 0xc1, 0x5e, 0x87, 0xd9, 0xdd, 0x49, 0x9f, 0xfe, 0xb1, 0xfc, 0xcb, 0xf1, 0x21, 0xa4, 0xb9, 0x08, 0xa8, 0x35, 0xe2, 0x05, 0x54, 0x49, 0x56,
0x0f, 0x7c, 0x67, 0x2c, 0x1c, 0xe6, 0x1d, 0xc9, 0x4f, 0x8e, 0x0f, 0x20, 0xcb, 0x85, 0x4f, 0xcd, 0x77, 0x1a, 0x37, 0x6b, 0xcb, 0x30, 0xb5, 0x28, 0x58, 0xaa, 0x9a, 0x46, 0x2e, 0x41, 0x66, 0x11,
0x11, 0x2f, 0xa2, 0x6a, 0xba, 0xb6, 0xd5, 0xb8, 0x69, 0x2c, 0x0a, 0x36, 0xc2, 0x60, 0x49, 0x35, 0x78, 0x1f, 0x52, 0x8f, 0x98, 0xe7, 0xb1, 0xf3, 0x82, 0x51, 0x41, 0xd5, 0x2d, 0xa2, 0x77, 0x18,
0xb5, 0x42, 0x8a, 0xcc, 0x22, 0xf0, 0x1e, 0x64, 0x1e, 0x33, 0xd7, 0x65, 0x67, 0x45, 0xad, 0x8a, 0xc3, 0x86, 0xb0, 0x5c, 0xaf, 0x90, 0xac, 0xa0, 0x6a, 0x92, 0xc8, 0x35, 0xbe, 0x0b, 0x9b, 0xdc,
0x6a, 0x1b, 0x24, 0xda, 0x61, 0x0c, 0x6b, 0xc2, 0x74, 0xdc, 0x62, 0xba, 0x8a, 0x6a, 0x69, 0x22, 0xf5, 0x07, 0xb4, 0xb0, 0x51, 0x41, 0xd5, 0x6c, 0xa3, 0x58, 0x53, 0x95, 0xd4, 0x66, 0x95, 0xd4,
0xd7, 0xf8, 0x2e, 0xac, 0x73, 0xc7, 0x1b, 0xd0, 0xe2, 0x5a, 0x15, 0xd5, 0xf2, 0x8d, 0x92, 0xa1, 0x7a, 0xb3, 0x4a, 0x88, 0x12, 0x9a, 0xdf, 0x20, 0xc8, 0x46, 0x89, 0xa9, 0x47, 0x07, 0x82, 0x05,
0xaa, 0x35, 0x66, 0xc2, 0x8d, 0xde, 0xac, 0x5a, 0xa2, 0x40, 0xfd, 0x1b, 0x04, 0xf9, 0x30, 0x31, 0xb8, 0x0e, 0x59, 0x4e, 0x83, 0x33, 0x77, 0x40, 0x4f, 0x5d, 0x5b, 0xe1, 0x66, 0x9a, 0x3b, 0xe1,
0x75, 0xe9, 0x40, 0x30, 0x1f, 0xd7, 0x21, 0xcf, 0xa9, 0x7f, 0xea, 0x0c, 0xe8, 0x89, 0x63, 0x29, 0xb4, 0x0c, 0x5d, 0xf5, 0xb8, 0xdd, 0xe2, 0x04, 0xb4, 0xa4, 0x6d, 0x73, 0x7c, 0x1b, 0xb6, 0x7c,
0xb9, 0xb9, 0xe6, 0x56, 0x30, 0xad, 0x40, 0x57, 0xfd, 0xdc, 0x6e, 0x71, 0x02, 0x11, 0xd2, 0xb6, 0x66, 0x2b, 0xb5, 0x21, 0xd5, 0xd9, 0x70, 0x5a, 0x4e, 0x1f, 0x31, 0x5b, 0x4a, 0xd3, 0xd1, 0xa1,
0x38, 0xbe, 0x0d, 0x1b, 0x1e, 0xb3, 0x14, 0xad, 0x49, 0x3a, 0x1f, 0x4c, 0x2b, 0xd9, 0x43, 0x66, 0xd6, 0x09, 0x8b, 0x0f, 0xa5, 0x2e, 0xb9, 0xd0, 0xf5, 0x2c, 0x3e, 0x94, 0xba, 0xe8, 0xb0, 0x6d,
0x49, 0x34, 0x1b, 0x1e, 0x46, 0x9c, 0x30, 0xf9, 0x50, 0x72, 0xe9, 0x39, 0xd7, 0x33, 0xf9, 0x50, 0x73, 0xf3, 0x31, 0x02, 0xe8, 0x30, 0xe7, 0x3d, 0xe6, 0x0b, 0xfa, 0x99, 0xc0, 0x77, 0x00, 0x16,
0x72, 0xe1, 0x61, 0xdb, 0xe2, 0xfa, 0x53, 0x04, 0xd0, 0x61, 0xf6, 0xfb, 0xcc, 0x13, 0xf4, 0x33, 0x3c, 0x05, 0x54, 0x41, 0xd5, 0x4c, 0x73, 0x3b, 0x9c, 0x96, 0x33, 0x73, 0x1c, 0x92, 0x99, 0xd3,
0x81, 0xef, 0x00, 0xcc, 0xf5, 0x14, 0x51, 0x15, 0xd5, 0x72, 0xcd, 0xcd, 0x60, 0x5a, 0xc9, 0xc5, 0xe0, 0x5b, 0x90, 0xd6, 0x30, 0xd2, 0xac, 0x4c, 0x13, 0xc2, 0x69, 0x39, 0xa5, 0x58, 0x48, 0x4a,
0x72, 0x48, 0x2e, 0x56, 0x83, 0x6f, 0x41, 0x36, 0x12, 0x23, 0xcd, 0xca, 0x35, 0x21, 0x98, 0x56, 0xa1, 0x44, 0x22, 0x4d, 0x22, 0xbd, 0xd3, 0x22, 0x05, 0x42, 0x52, 0x8a, 0xc3, 0xbc, 0x07, 0xe9,
0x32, 0x4a, 0x0b, 0xc9, 0x28, 0x29, 0x21, 0x14, 0x29, 0x91, 0xde, 0x45, 0x90, 0x12, 0x42, 0x32, 0x0e, 0x73, 0xee, 0x0b, 0x11, 0xe0, 0x1c, 0x24, 0x87, 0xf4, 0x73, 0x95, 0x9b, 0x44, 0x4b, 0xbc,
0x4a, 0x87, 0x7e, 0x0f, 0xb2, 0x1d, 0x66, 0x3f, 0x10, 0xc2, 0xc7, 0x05, 0x48, 0x0f, 0xe9, 0xe7, 0x07, 0x9b, 0x67, 0x96, 0x37, 0xa1, 0x2a, 0x09, 0x51, 0x1b, 0xf3, 0xc2, 0x90, 0xe4, 0x0f, 0x29,
0x2a, 0x37, 0x09, 0x97, 0x78, 0x17, 0xd6, 0x4f, 0x4d, 0x77, 0x42, 0x55, 0x12, 0xa2, 0x36, 0xfa, 0xe7, 0x96, 0x43, 0xf1, 0x3b, 0x90, 0x1e, 0xa8, 0x22, 0x64, 0x68, 0xb6, 0x51, 0x5a, 0x73, 0xe9,
0xb9, 0x26, 0x95, 0x3f, 0xa2, 0x9c, 0x9b, 0x36, 0xc5, 0xef, 0x42, 0x76, 0xa0, 0x8a, 0x90, 0xa1, 0xba, 0xd4, 0xe6, 0xc6, 0xe5, 0xb4, 0x9c, 0x20, 0xb3, 0x20, 0xfc, 0x36, 0x64, 0xe6, 0x7d, 0x27,
0xf9, 0x46, 0x79, 0xc5, 0xa5, 0x47, 0xa5, 0x36, 0xd7, 0x2e, 0xa6, 0x95, 0x14, 0x99, 0x05, 0xe1, 0x13, 0x3d, 0xff, 0x3e, 0x17, 0x62, 0xfc, 0x26, 0xa4, 0x54, 0xf3, 0xc8, 0xfa, 0x5e, 0xd4, 0x6d,
0x77, 0x20, 0x17, 0xf7, 0xa6, 0x4c, 0x74, 0xf5, 0x7d, 0xce, 0x61, 0xfc, 0x36, 0x64, 0x54, 0xf3, 0x44, 0x8b, 0xa3, 0x86, 0xb2, 0x2d, 0x61, 0xc9, 0xde, 0xb9, 0x46, 0xe4, 0x1a, 0xbf, 0x05, 0x9b,
0xc8, 0xfa, 0x5e, 0xd6, 0x6d, 0x24, 0x82, 0xc3, 0x86, 0xb2, 0x4c, 0x61, 0xca, 0xde, 0xb9, 0x46, 0x96, 0x10, 0x01, 0x2f, 0x6c, 0x56, 0x92, 0xd5, 0x6c, 0xe3, 0x95, 0x35, 0x6f, 0x8a, 0x7c, 0xd2,
0xe4, 0x1a, 0xdf, 0x87, 0x75, 0x53, 0x08, 0x9f, 0x17, 0xd7, 0xab, 0xe9, 0x5a, 0xbe, 0xf1, 0xda, 0xfc, 0x4a, 0x6f, 0x7e, 0x8f, 0x60, 0x4f, 0x8f, 0x42, 0x9f, 0x76, 0x98, 0xc3, 0x09, 0xfd, 0x74,
0x8a, 0x27, 0x85, 0x3e, 0x45, 0xfa, 0x15, 0xaf, 0x7f, 0x8f, 0x60, 0x37, 0x1a, 0x85, 0x3e, 0xed, 0x42, 0xb9, 0xc0, 0x87, 0xb0, 0xc5, 0x75, 0xb3, 0x69, 0x5f, 0xca, 0xeb, 0xf0, 0xb4, 0x8c, 0xcc,
0x30, 0x9b, 0x13, 0xfa, 0xe9, 0x84, 0x72, 0x81, 0x0f, 0x60, 0x83, 0x47, 0xcd, 0x16, 0xf9, 0x52, 0x03, 0x70, 0x0b, 0xd2, 0x4c, 0xcd, 0x94, 0x76, 0xe4, 0x60, 0x5d, 0xec, 0xf2, 0x14, 0x92, 0x59,
0x59, 0x25, 0x2f, 0xc2, 0x48, 0x1c, 0x80, 0x5b, 0x90, 0x65, 0x6a, 0xa6, 0x22, 0x47, 0xf6, 0x57, 0xa8, 0xf9, 0xf1, 0xbf, 0xd0, 0x66, 0x37, 0xf6, 0x2e, 0x6c, 0x8d, 0xd4, 0x52, 0x35, 0xfe, 0xfa,
0xc5, 0x2e, 0x4e, 0x21, 0x99, 0x85, 0xea, 0x9f, 0xfc, 0x4b, 0xda, 0xec, 0xc6, 0xde, 0x83, 0x8d, 0x2b, 0xd3, 0x11, 0xba, 0xe4, 0x79, 0x94, 0xf9, 0x2a, 0x14, 0x3b, 0x2e, 0x17, 0xd4, 0x8f, 0xe7,
0x91, 0x5a, 0xaa, 0xc6, 0x5f, 0x7d, 0x65, 0x51, 0x44, 0x54, 0x72, 0x1c, 0xa5, 0xbf, 0x0e, 0xa5, 0x9f, 0x95, 0x6e, 0xfe, 0x86, 0x20, 0x1f, 0x3f, 0x98, 0xe5, 0xdd, 0x07, 0x63, 0xde, 0xdb, 0xa9,
0x8e, 0xc3, 0x05, 0xf5, 0x92, 0xf9, 0x67, 0xa5, 0xeb, 0xbf, 0x21, 0xd8, 0x49, 0x1e, 0xcc, 0xf2, 0x70, 0x5a, 0x36, 0xda, 0x2d, 0x62, 0xb8, 0xf6, 0x15, 0xab, 0x8c, 0xff, 0x61, 0x55, 0xf2, 0xa5,
0xee, 0x81, 0x16, 0xf7, 0x76, 0x26, 0x98, 0x56, 0xb4, 0x76, 0x8b, 0x68, 0x8e, 0x75, 0xc9, 0x2a, 0xad, 0x8a, 0x3a, 0x7d, 0xe0, 0x31, 0xae, 0x3e, 0x28, 0x5b, 0x44, 0x6d, 0xcc, 0x1f, 0x11, 0xe0,
0xed, 0x7f, 0x58, 0x95, 0x7e, 0x65, 0xab, 0xc2, 0x4e, 0x1f, 0xb8, 0x8c, 0xab, 0x17, 0xca, 0x06, 0x0f, 0x27, 0x7d, 0xcf, 0xe5, 0x9f, 0xc4, 0xfd, 0x3b, 0x84, 0x5d, 0x1e, 0x7b, 0xd9, 0x62, 0x60,
0x51, 0x1b, 0xfd, 0x47, 0x04, 0xf8, 0xa3, 0x49, 0xdf, 0x75, 0xf8, 0x93, 0xa4, 0x7f, 0x07, 0xb0, 0x71, 0x38, 0x2d, 0xef, 0xc4, 0xf3, 0xb4, 0x5b, 0x64, 0x27, 0x2e, 0x6d, 0xdb, 0x57, 0xcc, 0x37,
0xcd, 0x13, 0x0f, 0x9b, 0x0f, 0x2c, 0x0e, 0xa6, 0x95, 0xad, 0x64, 0x9e, 0x76, 0x8b, 0x6c, 0x25, 0x5e, 0xc6, 0xfc, 0x05, 0x6b, 0x32, 0xce, 0x7a, 0x03, 0xf2, 0x31, 0x54, 0x42, 0xf9, 0x98, 0xf9,
0xd1, 0xb6, 0x75, 0xc9, 0x7c, 0xed, 0x55, 0xcc, 0x9f, 0x6b, 0x4d, 0x27, 0xb5, 0xde, 0x80, 0x9d, 0x9c, 0x1e, 0x3c, 0x45, 0x90, 0x99, 0x8f, 0x00, 0xbe, 0x03, 0xb8, 0x73, 0xfc, 0xfe, 0x69, 0xb7,
0x84, 0x54, 0x42, 0xf9, 0x98, 0x79, 0x9c, 0xee, 0x3f, 0x47, 0x90, 0x8b, 0x47, 0x00, 0xdf, 0x01, 0x47, 0x1e, 0xdc, 0x7f, 0x78, 0x7a, 0x72, 0xf4, 0xc1, 0xd1, 0xf1, 0x47, 0x47, 0xb9, 0x44, 0x71,
0xdc, 0x39, 0xfa, 0xe0, 0xa4, 0xdb, 0x23, 0x0f, 0x1f, 0x3c, 0x3a, 0x39, 0x3e, 0xfc, 0xf0, 0xf0, 0xef, 0xe2, 0x49, 0x25, 0x37, 0x97, 0x9d, 0xf8, 0x43, 0x9f, 0x9d, 0xfb, 0xf8, 0x00, 0xae, 0xc7,
0xe8, 0xe3, 0xc3, 0x42, 0xaa, 0xb4, 0x7b, 0xfe, 0xac, 0x5a, 0x88, 0xb1, 0x63, 0x6f, 0xe8, 0xb1, 0xd4, 0xdd, 0x5e, 0xeb, 0xf8, 0xa4, 0x97, 0x43, 0xc5, 0xfc, 0xc5, 0x93, 0xca, 0xee, 0x5c, 0xdc,
0x33, 0x0f, 0xef, 0xc3, 0xf5, 0x04, 0xdd, 0xed, 0xb5, 0x8e, 0x8e, 0x7b, 0x05, 0x54, 0xda, 0x39, 0x15, 0x36, 0x9b, 0x88, 0x65, 0xed, 0x03, 0x42, 0x72, 0xc6, 0xb2, 0x96, 0x06, 0x41, 0xf1, 0xfa,
0x7f, 0x56, 0xdd, 0x8e, 0xe1, 0xae, 0xb0, 0xd8, 0x44, 0x2c, 0xb2, 0x0f, 0x09, 0x29, 0x68, 0x8b, 0xd7, 0x3f, 0x94, 0x12, 0xbf, 0x3e, 0x2d, 0x2d, 0xc0, 0x1a, 0x8f, 0x11, 0x6c, 0x44, 0xdc, 0xf8,
0x2c, 0xf5, 0xfd, 0xd2, 0xf5, 0xaf, 0x7f, 0x28, 0xa7, 0x7e, 0x7d, 0x5e, 0x9e, 0x0b, 0x6b, 0x3c, 0x0b, 0xd8, 0xbe, 0xd2, 0xb3, 0xb8, 0xba, 0xca, 0x9d, 0x55, 0x13, 0x57, 0x7c, 0xb1, 0x52, 0x3b,
0x45, 0xb0, 0x16, 0xea, 0xc6, 0x5f, 0xc0, 0xe6, 0xa5, 0x9e, 0xc5, 0xb5, 0x65, 0xee, 0x2c, 0x9b, 0x6a, 0xde, 0xf8, 0xfd, 0xe7, 0xbf, 0xbf, 0x33, 0x76, 0x61, 0x5b, 0x2a, 0x5f, 0x1f, 0x59, 0xbe,
0xb8, 0xd2, 0xcb, 0xc9, 0xc8, 0x51, 0xfd, 0xc6, 0xef, 0x3f, 0xff, 0xfd, 0x9d, 0xb6, 0x0d, 0x9b, 0xe5, 0xd0, 0xe0, 0x2e, 0x6a, 0xfc, 0x64, 0x48, 0xb7, 0x9a, 0xf2, 0xf7, 0x14, 0x7f, 0x8b, 0x20,
0x92, 0x7c, 0x73, 0x64, 0x7a, 0xa6, 0x4d, 0xfd, 0xbb, 0xa8, 0xf1, 0x93, 0x26, 0xdd, 0x6a, 0xca, 0xbf, 0xa2, 0xcd, 0x71, 0x6d, 0xe5, 0x85, 0xad, 0x9d, 0x87, 0xe2, 0x6b, 0xcf, 0x01, 0x8b, 0x0f,
0xff, 0x5c, 0xfc, 0x2d, 0x82, 0x9d, 0x25, 0x6d, 0x8e, 0x8d, 0xa5, 0x17, 0xb6, 0x72, 0x1e, 0x4a, 0x88, 0x79, 0x4b, 0x72, 0xdd, 0x84, 0x6b, 0x8a, 0xeb, 0x9c, 0x05, 0x43, 0x1a, 0x2c, 0x51, 0xe2,
0x6f, 0x5c, 0x21, 0x2c, 0x39, 0x20, 0xfa, 0x2d, 0xa9, 0xeb, 0x26, 0x5c, 0x53, 0xba, 0xce, 0x98, 0xaf, 0x10, 0x64, 0x63, 0x77, 0x8d, 0x6f, 0xaf, 0x7a, 0xff, 0x72, 0xdf, 0xae, 0xe6, 0x58, 0xd1,
0x3f, 0xa4, 0xfe, 0x82, 0x4a, 0xfc, 0x15, 0x82, 0x7c, 0xe2, 0xae, 0xf1, 0xed, 0x65, 0xcf, 0x5f, 0x34, 0xff, 0x89, 0xa3, 0x8a, 0x9a, 0x85, 0xcb, 0x67, 0xa5, 0xc4, 0x9f, 0xcf, 0x4a, 0x89, 0x2f,
0xec, 0xdb, 0xe5, 0x3a, 0x96, 0x34, 0xcd, 0x7f, 0xd2, 0x51, 0x43, 0xcd, 0xe2, 0xc5, 0x8b, 0x72, 0xc3, 0x12, 0xba, 0x0c, 0x4b, 0xe8, 0x8f, 0xb0, 0x84, 0xfe, 0x0a, 0x4b, 0xa8, 0x9f, 0x92, 0x1f,
0xea, 0xcf, 0x17, 0xe5, 0xd4, 0x97, 0x41, 0x19, 0x5d, 0x04, 0x65, 0xf4, 0x47, 0x50, 0x46, 0x7f, 0xee, 0x37, 0xfe, 0x09, 0x00, 0x00, 0xff, 0xff, 0x08, 0xa1, 0xea, 0xc7, 0x9d, 0x08, 0x00, 0x00,
0x05, 0x65, 0xd4, 0xcf, 0xc8, 0x17, 0xf7, 0x5b, 0xff, 0x04, 0x00, 0x00, 0xff, 0xff, 0x95, 0x7b,
0x3c, 0x04, 0xe0, 0x08, 0x00, 0x00,
} }

View File

@ -4,7 +4,7 @@ package docker.swarmkit.v1;
import "gogoproto/gogo.proto"; import "gogoproto/gogo.proto";
import "google/protobuf/timestamp.proto"; import "google/protobuf/timestamp.proto";
import "github.com/docker/swarmkit/protobuf/plugin/plugin.proto"; import "plugin/plugin.proto";
// LogStream defines the stream from which the log message came. // LogStream defines the stream from which the log message came.
enum LogStream { enum LogStream {

View File

@ -1,5 +1,5 @@
// Code generated by protoc-gen-gogo. // Code generated by protoc-gen-gogo.
// source: github.com/docker/swarmkit/api/objects.proto // source: objects.proto
// DO NOT EDIT! // DO NOT EDIT!
package api package api
@ -7686,103 +7686,102 @@ var (
ErrIntOverflowObjects = fmt.Errorf("proto: integer overflow") ErrIntOverflowObjects = fmt.Errorf("proto: integer overflow")
) )
func init() { proto.RegisterFile("github.com/docker/swarmkit/api/objects.proto", fileDescriptorObjects) } func init() { proto.RegisterFile("objects.proto", fileDescriptorObjects) }
var fileDescriptorObjects = []byte{ var fileDescriptorObjects = []byte{
// 1513 bytes of a gzipped FileDescriptorProto // 1491 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x58, 0x4d, 0x6f, 0x1b, 0x4f, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x58, 0xcf, 0x6f, 0x1b, 0x4f,
0x19, 0xef, 0xda, 0x1b, 0xbf, 0x3c, 0x4e, 0x4c, 0x98, 0x7f, 0x08, 0x5b, 0x13, 0xec, 0xe0, 0x0a, 0x15, 0xef, 0xda, 0x1b, 0xff, 0x78, 0x4e, 0x4c, 0x98, 0x86, 0xb0, 0x35, 0xc1, 0x0e, 0xae, 0x40,
0x54, 0x55, 0x95, 0x53, 0x42, 0x81, 0x34, 0x50, 0x5a, 0x3b, 0x89, 0x5a, 0xab, 0x94, 0x46, 0xd3, 0x15, 0xaa, 0x9c, 0x12, 0x0a, 0x4a, 0x03, 0xa5, 0xb5, 0x93, 0xa8, 0xb5, 0x4a, 0x69, 0x34, 0x2d,
0xd2, 0x72, 0x5b, 0x26, 0xbb, 0x53, 0x77, 0xf1, 0x7a, 0x67, 0xb5, 0x33, 0x76, 0xf1, 0x8d, 0x73, 0x2d, 0xb7, 0x65, 0xb2, 0x3b, 0x75, 0x17, 0xaf, 0x77, 0x56, 0x3b, 0x63, 0x17, 0xdf, 0x38, 0x87,
0xf8, 0x00, 0xb9, 0x71, 0xe8, 0x57, 0x80, 0x0b, 0x17, 0x0e, 0x9c, 0x7a, 0xe4, 0x84, 0x38, 0x45, 0x3f, 0x20, 0x37, 0x0e, 0xfd, 0x17, 0xe0, 0xc2, 0x85, 0x03, 0xa7, 0x1e, 0x39, 0x21, 0x4e, 0x11,
0xd4, 0xdf, 0x02, 0x89, 0x03, 0x9a, 0xd9, 0x59, 0x7b, 0x13, 0xaf, 0x93, 0x14, 0x55, 0xd1, 0xff, 0xf5, 0x7f, 0x81, 0xc4, 0xe1, 0xab, 0x99, 0x9d, 0xb5, 0x37, 0xf1, 0x3a, 0x49, 0xbf, 0xaa, 0xa2,
0x94, 0x99, 0x9d, 0xdf, 0xef, 0x79, 0x9b, 0xe7, 0x65, 0x62, 0xb8, 0xdb, 0xf3, 0xc4, 0xbb, 0xe1, 0xef, 0x29, 0x33, 0x3b, 0x9f, 0xcf, 0x9b, 0xf7, 0xde, 0xbc, 0x5f, 0x31, 0xac, 0xb0, 0xa3, 0x3f,
0x51, 0xcb, 0x61, 0x83, 0x2d, 0x97, 0x39, 0x7d, 0x1a, 0x6d, 0xf1, 0xf7, 0x24, 0x1a, 0xf4, 0x3d, 0x50, 0x47, 0xf0, 0x56, 0x18, 0x31, 0xc1, 0x10, 0x72, 0x99, 0xd3, 0xa7, 0x51, 0x8b, 0xbf, 0x27,
0xb1, 0x45, 0x42, 0x6f, 0x8b, 0x1d, 0xfd, 0x8e, 0x3a, 0x82, 0xb7, 0xc2, 0x88, 0x09, 0x86, 0x50, 0xd1, 0xa0, 0xef, 0x89, 0xd6, 0xe8, 0x27, 0xb5, 0x8a, 0x18, 0x87, 0x54, 0x03, 0x6a, 0x15, 0x1e,
0x0c, 0x69, 0x25, 0x90, 0xd6, 0xe8, 0x87, 0xb5, 0x3b, 0x97, 0x48, 0x10, 0xe3, 0x90, 0x6a, 0xfe, 0x52, 0x27, 0xd9, 0x34, 0x7a, 0x8c, 0xf5, 0x7c, 0xba, 0xa5, 0x76, 0x47, 0xc3, 0xb7, 0x5b, 0xc2,
0xa5, 0x58, 0x1e, 0x52, 0x27, 0xc1, 0x36, 0x7a, 0x8c, 0xf5, 0x7c, 0xba, 0xa5, 0x76, 0x47, 0xc3, 0x1b, 0x50, 0x2e, 0xc8, 0x20, 0xd4, 0x80, 0xb5, 0x1e, 0xeb, 0x31, 0xb5, 0xdc, 0x92, 0x2b, 0xfd,
0xb7, 0x5b, 0xc2, 0x1b, 0x50, 0x2e, 0xc8, 0x20, 0xd4, 0x80, 0xb5, 0x1e, 0xeb, 0x31, 0xb5, 0xdc, 0xf5, 0xd6, 0x79, 0x1a, 0x09, 0xc6, 0xfa, 0xe8, 0x66, 0xe8, 0x0f, 0x7b, 0x5e, 0xb0, 0x15, 0xff,
0x92, 0x2b, 0xfd, 0xf5, 0xe6, 0x79, 0x1a, 0x09, 0xc6, 0xfa, 0xe8, 0xa7, 0x17, 0x68, 0x9f, 0xc2, 0x89, 0x3f, 0x36, 0xff, 0x6e, 0x80, 0xf9, 0x9c, 0x0a, 0x82, 0x7e, 0x01, 0xc5, 0x11, 0x8d, 0xb8,
0x43, 0x7f, 0xd8, 0xf3, 0x02, 0xfd, 0x27, 0x26, 0x36, 0xff, 0x6a, 0x80, 0xf9, 0x9c, 0x0a, 0x82, 0xc7, 0x02, 0xcb, 0xd8, 0x34, 0xee, 0x54, 0xb6, 0xbf, 0xd7, 0x9a, 0xd7, 0xb7, 0xf5, 0x3a, 0x86,
0x7e, 0x06, 0xc5, 0x11, 0x8d, 0xb8, 0xc7, 0x02, 0xcb, 0xd8, 0x34, 0x6e, 0x57, 0xb6, 0xbf, 0xd3, 0x74, 0xcc, 0x8f, 0xa7, 0x8d, 0x1b, 0x38, 0x61, 0xa0, 0x07, 0x00, 0x4e, 0x44, 0x89, 0xa0, 0xae,
0x9a, 0x8f, 0x48, 0xeb, 0x75, 0x0c, 0xe9, 0x98, 0x1f, 0x4f, 0x1b, 0x37, 0x70, 0xc2, 0x40, 0x0f, 0x4d, 0x84, 0x95, 0x53, 0xfc, 0x5a, 0x2b, 0x56, 0xa5, 0x95, 0xa8, 0xd2, 0x7a, 0x95, 0x58, 0x80,
0x00, 0x9c, 0x88, 0x12, 0x41, 0x5d, 0x9b, 0x08, 0x2b, 0xa7, 0xf8, 0xb5, 0x56, 0x6c, 0x6e, 0x2b, 0xcb, 0x1a, 0xdd, 0x16, 0x92, 0x3a, 0x0c, 0xdd, 0x84, 0x9a, 0xbf, 0x9c, 0xaa, 0xd1, 0x6d, 0xd1,
0xd1, 0xdf, 0x7a, 0x95, 0x78, 0x89, 0xcb, 0x1a, 0xdd, 0x16, 0x92, 0x3a, 0x0c, 0xdd, 0x84, 0x9a, 0xfc, 0xab, 0x09, 0xe6, 0x6f, 0x98, 0x4b, 0xd1, 0x3a, 0xe4, 0x3c, 0x57, 0xa9, 0x5d, 0xee, 0x14,
0xbf, 0x9c, 0xaa, 0xd1, 0x6d, 0xd1, 0xfc, 0xb3, 0x09, 0xe6, 0xaf, 0x98, 0x4b, 0xd1, 0x3a, 0xe4, 0x26, 0xa7, 0x8d, 0x5c, 0x77, 0x1f, 0xe7, 0x3c, 0x17, 0x6d, 0x83, 0x39, 0xa0, 0x82, 0x68, 0x85,
0x3c, 0x57, 0x99, 0x5d, 0xee, 0x14, 0x26, 0xa7, 0x8d, 0x5c, 0x77, 0x1f, 0xe7, 0x3c, 0x17, 0x6d, 0xac, 0x2c, 0x83, 0xa4, 0xed, 0xda, 0x1a, 0x85, 0x45, 0x3f, 0x07, 0x53, 0x3e, 0x83, 0xd6, 0x64,
0x83, 0x39, 0xa0, 0x82, 0x68, 0x83, 0xac, 0x2c, 0x87, 0xa4, 0xef, 0xda, 0x1b, 0x85, 0x45, 0x3f, 0x23, 0x8b, 0x23, 0xef, 0x7c, 0x19, 0x52, 0x27, 0xe1, 0x49, 0x3c, 0x3a, 0x80, 0x8a, 0x4b, 0xb9,
0x01, 0x53, 0x5e, 0x95, 0xb6, 0x64, 0x23, 0x8b, 0x23, 0x75, 0xbe, 0x0c, 0xa9, 0x93, 0xf0, 0x24, 0x13, 0x79, 0xa1, 0x90, 0x3e, 0x34, 0x15, 0xfd, 0xf6, 0x22, 0xfa, 0xfe, 0x0c, 0x8a, 0xd3, 0x3c,
0x1e, 0x1d, 0x40, 0xc5, 0xa5, 0xdc, 0x89, 0xbc, 0x50, 0xc8, 0x18, 0x9a, 0x8a, 0x7e, 0x6b, 0x11, 0xf4, 0x4b, 0x28, 0x70, 0x41, 0xc4, 0x90, 0x5b, 0x4b, 0x4a, 0x42, 0x7d, 0xa1, 0x02, 0x0a, 0xa5,
0x7d, 0x7f, 0x06, 0xc5, 0x69, 0x1e, 0xfa, 0x39, 0x14, 0xb8, 0x20, 0x62, 0xc8, 0xad, 0x25, 0x25, 0x55, 0xd0, 0x1c, 0xf4, 0x14, 0xaa, 0x03, 0x12, 0x90, 0x1e, 0x8d, 0x6c, 0x2d, 0xa5, 0xa0, 0xa4,
0xa1, 0xbe, 0xd0, 0x00, 0x85, 0xd2, 0x26, 0x68, 0x0e, 0x7a, 0x0a, 0xd5, 0x01, 0x09, 0x48, 0x8f, 0xfc, 0x20, 0xd3, 0xf4, 0x18, 0x19, 0x0b, 0xc2, 0x2b, 0x83, 0xf4, 0x16, 0x1d, 0x00, 0x10, 0x21,
0x46, 0xb6, 0x96, 0x52, 0x50, 0x52, 0xbe, 0x97, 0xe9, 0x7a, 0x8c, 0x8c, 0x05, 0xe1, 0x95, 0x41, 0x88, 0xf3, 0x6e, 0x40, 0x03, 0x61, 0x15, 0x95, 0x94, 0x1f, 0x66, 0xea, 0x42, 0xc5, 0x7b, 0x16,
0x7a, 0x8b, 0x0e, 0x00, 0x88, 0x10, 0xc4, 0x79, 0x37, 0xa0, 0x81, 0xb0, 0x8a, 0x4a, 0xca, 0xf7, 0xf5, 0xdb, 0x53, 0x30, 0x4e, 0x11, 0xd1, 0x13, 0xa8, 0x38, 0x34, 0x12, 0xde, 0x5b, 0xcf, 0x21,
0x33, 0x6d, 0xa1, 0xe2, 0x3d, 0x8b, 0xfa, 0xed, 0x29, 0x18, 0xa7, 0x88, 0xe8, 0x09, 0x54, 0x1c, 0x82, 0x5a, 0x25, 0x25, 0xa7, 0x91, 0x25, 0x67, 0x6f, 0x06, 0xd3, 0x46, 0xa5, 0x99, 0xe8, 0x1e,
0x1a, 0x09, 0xef, 0xad, 0xe7, 0x10, 0x41, 0xad, 0x92, 0x92, 0xd3, 0xc8, 0x92, 0xb3, 0x37, 0x83, 0x98, 0x11, 0xf3, 0xa9, 0x55, 0xde, 0x34, 0xee, 0x54, 0x17, 0x3f, 0x0b, 0x66, 0x3e, 0xc5, 0x0a,
0x69, 0xa7, 0xd2, 0x4c, 0x74, 0x0f, 0xcc, 0x88, 0xf9, 0xd4, 0x2a, 0x6f, 0x1a, 0xb7, 0xab, 0x8b, 0xb9, 0xbb, 0x7e, 0x7c, 0xd2, 0x44, 0xb0, 0x5a, 0x32, 0x56, 0x0d, 0x15, 0x1a, 0xc6, 0x3d, 0xe3,
0xaf, 0x05, 0x33, 0x9f, 0x62, 0x85, 0xdc, 0x5d, 0x3f, 0x3e, 0x69, 0x22, 0x58, 0x2d, 0x19, 0xab, 0x77, 0xc6, 0xef, 0x8d, 0xe6, 0xff, 0xf3, 0x50, 0x7c, 0x49, 0xa3, 0x91, 0xe7, 0x7c, 0xd9, 0xc0,
0x86, 0x4a, 0x0d, 0xe3, 0x9e, 0xf1, 0x1b, 0xe3, 0xb7, 0x46, 0xf3, 0xbf, 0x79, 0x28, 0xbe, 0xa4, 0x79, 0x70, 0x26, 0x70, 0x32, 0x6d, 0xd4, 0xd7, 0xce, 0xc5, 0xce, 0x0e, 0x94, 0x68, 0xe0, 0x86,
0xd1, 0xc8, 0x73, 0xbe, 0x6c, 0xe2, 0x3c, 0x38, 0x93, 0x38, 0x99, 0x3e, 0x6a, 0xb5, 0x73, 0xb9, 0xcc, 0x0b, 0x84, 0x0e, 0x9c, 0x4c, 0x03, 0x0f, 0x34, 0x06, 0x4f, 0xd1, 0xe8, 0x00, 0x56, 0xe2,
0xb3, 0x03, 0x25, 0x1a, 0xb8, 0x21, 0xf3, 0x02, 0xa1, 0x13, 0x27, 0xd3, 0xc1, 0x03, 0x8d, 0xc1, 0x7c, 0xb0, 0xcf, 0x44, 0xcd, 0x66, 0x16, 0xfd, 0xb7, 0x0a, 0xa8, 0x9f, 0x7b, 0x79, 0x98, 0xda,
0x53, 0x34, 0x3a, 0x80, 0x95, 0xb8, 0x1e, 0xec, 0x33, 0x59, 0xb3, 0x99, 0x45, 0xff, 0xb5, 0x02, 0xa1, 0x7d, 0x58, 0x09, 0x23, 0x3a, 0xf2, 0xd8, 0x90, 0xdb, 0xca, 0x88, 0xc2, 0x95, 0x8c, 0xc0,
0xea, 0xeb, 0x5e, 0x1e, 0xa6, 0x76, 0x68, 0x1f, 0x56, 0xc2, 0x88, 0x8e, 0x3c, 0x36, 0xe4, 0xb6, 0xcb, 0x09, 0x4b, 0xee, 0xd0, 0xaf, 0x60, 0x59, 0x92, 0xed, 0xa4, 0x8e, 0xc0, 0xa5, 0x75, 0x04,
0x72, 0xa2, 0x70, 0x25, 0x27, 0xf0, 0x72, 0xc2, 0x92, 0x3b, 0xf4, 0x0b, 0x58, 0x96, 0x64, 0x3b, 0xab, 0x92, 0xa7, 0x37, 0xe8, 0x05, 0x7c, 0xe7, 0x8c, 0x16, 0x53, 0x41, 0x95, 0xcb, 0x05, 0xdd,
0xe9, 0x23, 0x70, 0x69, 0x1f, 0xc1, 0x15, 0x49, 0xd0, 0x1b, 0xf4, 0x02, 0xbe, 0x75, 0xc6, 0x8a, 0x4c, 0x6b, 0xa2, 0x3f, 0xee, 0xa2, 0xe3, 0x93, 0x66, 0x15, 0x96, 0xd3, 0x21, 0xd0, 0xfc, 0x4b,
0xa9, 0xa0, 0xca, 0xe5, 0x82, 0xbe, 0x4a, 0x5b, 0xa2, 0x3f, 0xee, 0xa2, 0xe3, 0x93, 0x66, 0x15, 0x0e, 0x4a, 0x89, 0x23, 0xd1, 0x7d, 0xfd, 0x66, 0xc6, 0x62, 0xaf, 0x25, 0x58, 0x65, 0x6f, 0xfc,
0x96, 0xd3, 0x29, 0xd0, 0xfc, 0x53, 0x0e, 0x4a, 0x49, 0x20, 0xd1, 0x7d, 0x7d, 0x67, 0xc6, 0xe2, 0x5c, 0xf7, 0x61, 0x29, 0x64, 0x91, 0xe0, 0x56, 0x6e, 0x33, 0xbf, 0x28, 0x45, 0x0f, 0x59, 0x24,
0xa8, 0x25, 0x58, 0xe5, 0x6f, 0x7c, 0x5d, 0xf7, 0x61, 0x29, 0x64, 0x91, 0xe0, 0x56, 0x6e, 0x33, 0xf6, 0x58, 0xf0, 0xd6, 0xeb, 0xe1, 0x18, 0x8c, 0xde, 0x40, 0x65, 0xe4, 0x45, 0x62, 0x48, 0x7c,
0xbf, 0xa8, 0x44, 0x0f, 0x59, 0x24, 0xf6, 0x58, 0xf0, 0xd6, 0xeb, 0xe1, 0x18, 0x8c, 0xde, 0x40, 0xdb, 0x0b, 0xb9, 0x95, 0x57, 0xdc, 0x1f, 0x5d, 0x74, 0x65, 0xeb, 0x75, 0x8c, 0xef, 0x1e, 0x76,
0x65, 0xe4, 0x45, 0x62, 0x48, 0x7c, 0xdb, 0x0b, 0xb9, 0x95, 0x57, 0xdc, 0x1f, 0x5c, 0xa4, 0xb2, 0xaa, 0x93, 0xd3, 0x06, 0x4c, 0xb7, 0x1c, 0x83, 0x16, 0xd5, 0x0d, 0x79, 0xed, 0x39, 0x94, 0xa7,
0xf5, 0x3a, 0xc6, 0x77, 0x0f, 0x3b, 0xd5, 0xc9, 0x69, 0x03, 0xa6, 0x5b, 0x8e, 0x41, 0x8b, 0xea, 0x27, 0xe8, 0x2e, 0x40, 0x10, 0x67, 0xa4, 0x3d, 0x8d, 0xec, 0x95, 0xc9, 0x69, 0xa3, 0xac, 0xf3,
0x86, 0xbc, 0xf6, 0x1c, 0xca, 0xd3, 0x13, 0x74, 0x17, 0x20, 0x88, 0x2b, 0xd2, 0x9e, 0x66, 0xf6, 0xb4, 0xbb, 0x8f, 0xcb, 0x1a, 0xd0, 0x75, 0x11, 0x02, 0x93, 0xb8, 0x6e, 0xa4, 0xe2, 0xbc, 0x8c,
0xca, 0xe4, 0xb4, 0x51, 0xd6, 0x75, 0xda, 0xdd, 0xc7, 0x65, 0x0d, 0xe8, 0xba, 0x08, 0x81, 0x49, 0xd5, 0xba, 0xf9, 0xe7, 0x22, 0x98, 0xaf, 0x08, 0xef, 0x5f, 0x77, 0x55, 0x95, 0x77, 0xce, 0x65,
0x5c, 0x37, 0x52, 0x79, 0x5e, 0xc6, 0x6a, 0xdd, 0xfc, 0x63, 0x11, 0xcc, 0x57, 0x84, 0xf7, 0xaf, 0xc6, 0x5d, 0x00, 0x1e, 0xc7, 0x9b, 0x34, 0xc7, 0x9c, 0x99, 0xa3, 0xa3, 0x50, 0x9a, 0xa3, 0x01,
0xbb, 0xab, 0x4a, 0x9d, 0x73, 0x95, 0x71, 0x17, 0x80, 0xc7, 0xf9, 0x26, 0xdd, 0x31, 0x67, 0xee, 0xb1, 0x39, 0xdc, 0x67, 0x42, 0x25, 0x81, 0x89, 0xd5, 0x1a, 0xdd, 0x86, 0x62, 0xc0, 0x5c, 0x45,
0xe8, 0x2c, 0x94, 0xee, 0x68, 0x40, 0xec, 0x0e, 0xf7, 0x99, 0x50, 0x45, 0x60, 0x62, 0xb5, 0x46, 0x2f, 0x28, 0x3a, 0x4c, 0x4e, 0x1b, 0x05, 0x59, 0x2b, 0xba, 0xfb, 0xb8, 0x20, 0x8f, 0xba, 0xae,
0xb7, 0xa0, 0x18, 0x30, 0x57, 0xd1, 0x0b, 0x8a, 0x0e, 0x93, 0xd3, 0x46, 0x41, 0xf6, 0x8a, 0xee, 0x2c, 0x53, 0x24, 0x08, 0x98, 0x20, 0xb2, 0x06, 0x73, 0x5d, 0xee, 0x32, 0xa3, 0xbf, 0x3d, 0x83,
0x3e, 0x2e, 0xc8, 0xa3, 0xae, 0x2b, 0xdb, 0x14, 0x09, 0x02, 0x26, 0x88, 0xec, 0xc1, 0x5c, 0xb7, 0x25, 0x65, 0x2a, 0xc5, 0x44, 0xaf, 0xe1, 0x66, 0xa2, 0x6f, 0x5a, 0x60, 0xe9, 0x73, 0x04, 0x22,
0xbb, 0xcc, 0xec, 0x6f, 0xcf, 0x60, 0x49, 0x9b, 0x4a, 0x31, 0xd1, 0x6b, 0xf8, 0x2a, 0xb1, 0x37, 0x2d, 0x21, 0x75, 0x92, 0x6a, 0x0b, 0xe5, 0xc5, 0x6d, 0x41, 0x79, 0x30, 0xab, 0x2d, 0x74, 0x60,
0x2d, 0xb0, 0xf4, 0x39, 0x02, 0x91, 0x96, 0x90, 0x3a, 0x49, 0x8d, 0x85, 0xf2, 0xe2, 0xb1, 0xa0, 0xc5, 0xa5, 0xdc, 0x8b, 0xa8, 0xab, 0xca, 0x04, 0x55, 0x99, 0x59, 0xdd, 0xfe, 0xfe, 0x45, 0x42,
0x22, 0x98, 0x35, 0x16, 0x3a, 0xb0, 0xe2, 0x52, 0xee, 0x45, 0xd4, 0x55, 0x6d, 0x82, 0xaa, 0xca, 0x28, 0x5e, 0xd6, 0x1c, 0xb5, 0x43, 0x6d, 0x28, 0xe9, 0xb8, 0xe1, 0x56, 0x45, 0xc5, 0xee, 0x15,
0xac, 0x6e, 0x7f, 0xf7, 0x22, 0x21, 0x14, 0x2f, 0x6b, 0x8e, 0xda, 0xa1, 0x36, 0x94, 0x74, 0xde, 0xdb, 0xc1, 0x94, 0x76, 0xa6, 0xcc, 0x2d, 0x7f, 0x56, 0x99, 0x7b, 0x00, 0xe0, 0xb3, 0x9e, 0xed,
0x70, 0xab, 0xa2, 0x72, 0xf7, 0x8a, 0xe3, 0x60, 0x4a, 0x3b, 0xd3, 0xe6, 0x96, 0x3f, 0xab, 0xcd, 0x46, 0xde, 0x88, 0x46, 0xd6, 0x8a, 0x1e, 0x12, 0x32, 0xb8, 0xfb, 0x0a, 0x81, 0xcb, 0x3e, 0xeb,
0x3d, 0x00, 0xf0, 0x59, 0xcf, 0x76, 0x23, 0x6f, 0x44, 0x23, 0x6b, 0x45, 0x3f, 0x12, 0x32, 0xb8, 0xc5, 0xcb, 0xb9, 0xa2, 0x54, 0xfd, 0xcc, 0xa2, 0x44, 0xa0, 0x46, 0x38, 0xf7, 0x7a, 0x01, 0x75,
0xfb, 0x0a, 0x81, 0xcb, 0x3e, 0xeb, 0xc5, 0xcb, 0xb9, 0xa6, 0x54, 0xfd, 0xcc, 0xa6, 0x44, 0xa0, 0xed, 0x1e, 0x0d, 0x68, 0xe4, 0x39, 0x76, 0x44, 0x39, 0x1b, 0x46, 0x0e, 0xe5, 0xd6, 0xb7, 0x94,
0x46, 0x38, 0xf7, 0x7a, 0x01, 0x75, 0xed, 0x1e, 0x0d, 0x68, 0xe4, 0x39, 0x76, 0x44, 0x39, 0x1b, 0x27, 0x32, 0xdb, 0xfc, 0x93, 0x18, 0x8c, 0x35, 0x16, 0x5b, 0x89, 0x98, 0x73, 0x07, 0x7c, 0xb7,
0x46, 0x0e, 0xe5, 0xd6, 0x37, 0x54, 0x24, 0x32, 0xc7, 0xfc, 0x93, 0x18, 0x8c, 0x35, 0x16, 0x5b, 0x76, 0x7c, 0xd2, 0x5c, 0x87, 0xb5, 0x74, 0x99, 0xda, 0x31, 0x1e, 0x1b, 0x4f, 0x8d, 0x43, 0xa3,
0x89, 0x98, 0x73, 0x07, 0x7c, 0xb7, 0x76, 0x7c, 0xd2, 0x5c, 0x87, 0xb5, 0x74, 0x9b, 0xda, 0x31, 0xf9, 0xcf, 0x1c, 0x7c, 0x7b, 0xce, 0xa7, 0xe8, 0x67, 0x50, 0xd4, 0x5e, 0xbd, 0x68, 0x58, 0xd3,
0x1e, 0x1b, 0x4f, 0x8d, 0x43, 0xa3, 0xf9, 0xf7, 0x1c, 0x7c, 0x73, 0x2e, 0xa6, 0xe8, 0xc7, 0x50, 0x3c, 0x9c, 0x60, 0xd1, 0x06, 0x94, 0x65, 0x8a, 0x53, 0xce, 0x69, 0x5c, 0xbc, 0xca, 0x78, 0xf6,
0xd4, 0x51, 0xbd, 0xe8, 0xb1, 0xa6, 0x79, 0x38, 0xc1, 0xa2, 0x0d, 0x28, 0xcb, 0x12, 0xa7, 0x9c, 0x01, 0x59, 0x50, 0x24, 0xbe, 0x47, 0xe4, 0x59, 0x5e, 0x9d, 0x25, 0x5b, 0x34, 0x84, 0xf5, 0xd8,
0xd3, 0xb8, 0x79, 0x95, 0xf1, 0xec, 0x03, 0xb2, 0xa0, 0x48, 0x7c, 0x8f, 0xc8, 0xb3, 0xbc, 0x3a, 0xf5, 0xf6, 0xac, 0xb5, 0xdb, 0x2c, 0x14, 0xdc, 0x32, 0x95, 0xfd, 0x8f, 0xae, 0x14, 0x09, 0xfa,
0x4b, 0xb6, 0x68, 0x08, 0xeb, 0x71, 0xe8, 0xed, 0xd9, 0x68, 0xb7, 0x59, 0x28, 0xb8, 0x65, 0x2a, 0x71, 0x66, 0x1f, 0x5e, 0x84, 0x82, 0x1f, 0x04, 0x22, 0x1a, 0xe3, 0x35, 0x37, 0xe3, 0xa8, 0xf6,
0xff, 0x1f, 0x5d, 0x29, 0x13, 0xf4, 0xe5, 0xcc, 0x3e, 0xbc, 0x08, 0x05, 0x3f, 0x08, 0x44, 0x34, 0x04, 0x6e, 0x2d, 0xa4, 0xa0, 0x55, 0xc8, 0xf7, 0xe9, 0x38, 0x2e, 0x4f, 0x58, 0x2e, 0xd1, 0x1a,
0xc6, 0x6b, 0x6e, 0xc6, 0x51, 0xed, 0x09, 0xdc, 0x5c, 0x48, 0x41, 0xab, 0x90, 0xef, 0xd3, 0x71, 0x2c, 0x8d, 0x88, 0x3f, 0xa4, 0xba, 0x9a, 0xc5, 0x9b, 0xdd, 0xdc, 0x8e, 0xd1, 0xfc, 0x90, 0x83,
0xdc, 0x9e, 0xb0, 0x5c, 0xa2, 0x35, 0x58, 0x1a, 0x11, 0x7f, 0x48, 0x75, 0x37, 0x8b, 0x37, 0xbb, 0xa2, 0x56, 0xe7, 0xba, 0x5b, 0xbe, 0xbe, 0x76, 0xae, 0xb0, 0x3d, 0x84, 0x65, 0xed, 0xd2, 0x38,
0xb9, 0x1d, 0xa3, 0xf9, 0x21, 0x07, 0x45, 0x6d, 0xce, 0x75, 0x8f, 0x7c, 0xad, 0x76, 0xae, 0xb1, 0x23, 0xcd, 0x4b, 0x63, 0xba, 0x12, 0xe3, 0xe3, 0x6c, 0x7c, 0x08, 0xa6, 0x17, 0x92, 0x81, 0x6e,
0x3d, 0x84, 0x65, 0x1d, 0xd2, 0xb8, 0x22, 0xcd, 0x4b, 0x73, 0xba, 0x12, 0xe3, 0xe3, 0x6a, 0x7c, 0xf7, 0x99, 0x37, 0x77, 0x0f, 0xdb, 0xcf, 0x5f, 0x84, 0x71, 0x61, 0x29, 0x4d, 0x4e, 0x1b, 0xa6,
0x08, 0xa6, 0x17, 0x92, 0x81, 0x1e, 0xf7, 0x99, 0x9a, 0xbb, 0x87, 0xed, 0xe7, 0x2f, 0xc2, 0xb8, 0xfc, 0x80, 0x15, 0x2d, 0xb3, 0x31, 0xfe, 0x6d, 0x09, 0x8a, 0x7b, 0xfe, 0x90, 0x0b, 0x1a, 0x5d,
0xb1, 0x94, 0x26, 0xa7, 0x0d, 0x53, 0x7e, 0xc0, 0x8a, 0x96, 0x39, 0x18, 0xff, 0xb2, 0x04, 0xc5, 0xb7, 0x93, 0xf4, 0xb5, 0x73, 0x4e, 0xda, 0x83, 0x62, 0xc4, 0x98, 0xb0, 0x1d, 0x72, 0x91, 0x7f,
0x3d, 0x7f, 0xc8, 0x05, 0x8d, 0xae, 0x3b, 0x48, 0x5a, 0xed, 0x5c, 0x90, 0xf6, 0xa0, 0x18, 0x31, 0x30, 0x63, 0x62, 0xaf, 0xdd, 0xa9, 0x4a, 0xa2, 0xac, 0xed, 0xf1, 0x1e, 0x17, 0x24, 0x75, 0x8f,
0x26, 0x6c, 0x87, 0x5c, 0x14, 0x1f, 0xcc, 0x98, 0xd8, 0x6b, 0x77, 0xaa, 0x92, 0x28, 0x7b, 0x7b, 0xa0, 0x37, 0xb0, 0x9e, 0x74, 0xc4, 0x23, 0xc6, 0x04, 0x17, 0x11, 0x09, 0xed, 0x3e, 0x1d, 0xcb,
0xbc, 0xc7, 0x05, 0x49, 0xdd, 0x23, 0xe8, 0x0d, 0xac, 0x27, 0x13, 0xf1, 0x88, 0x31, 0xc1, 0x45, 0x59, 0x29, 0xbf, 0x68, 0x36, 0x3e, 0x08, 0x9c, 0x68, 0xac, 0x9c, 0xf7, 0x8c, 0x8e, 0xf1, 0x9a,
0x44, 0x42, 0xbb, 0x4f, 0xc7, 0xf2, 0xad, 0x94, 0x5f, 0xf4, 0x36, 0x3e, 0x08, 0x9c, 0x68, 0xac, 0x16, 0xd0, 0x49, 0xf8, 0xcf, 0xe8, 0x98, 0xa3, 0x47, 0xb0, 0x41, 0xa7, 0x30, 0x29, 0xd1, 0xf6,
0x82, 0xf7, 0x8c, 0x8e, 0xf1, 0x9a, 0x16, 0xd0, 0x49, 0xf8, 0xcf, 0xe8, 0x98, 0xa3, 0x47, 0xb0, 0xc9, 0x40, 0xf6, 0x7a, 0xdb, 0xf1, 0x99, 0xd3, 0x57, 0xed, 0xc6, 0xc4, 0xb7, 0x68, 0x5a, 0xd4,
0x41, 0xa7, 0x30, 0x29, 0xd1, 0xf6, 0xc9, 0x40, 0xce, 0x7a, 0xdb, 0xf1, 0x99, 0xd3, 0x57, 0xe3, 0xaf, 0x63, 0xc4, 0x9e, 0x04, 0x20, 0x0e, 0xd6, 0x91, 0x4f, 0x9c, 0xbe, 0xef, 0x71, 0xf9, 0xef,
0xc6, 0xc4, 0x37, 0x69, 0x5a, 0xd4, 0x2f, 0x63, 0xc4, 0x9e, 0x04, 0x20, 0x0e, 0xd6, 0x91, 0x4f, 0x4f, 0x6a, 0xdc, 0x95, 0x1d, 0x43, 0xea, 0xb6, 0x73, 0x81, 0xb7, 0x5a, 0x9d, 0x19, 0x37, 0x35,
0x9c, 0xbe, 0xef, 0x71, 0xf9, 0xef, 0x4f, 0xea, 0xb9, 0x2b, 0x27, 0x86, 0xb4, 0x6d, 0xe7, 0x82, 0x3c, 0xeb, 0x8c, 0xfa, 0xee, 0x51, 0xf6, 0x29, 0xea, 0x40, 0x65, 0x18, 0xc8, 0xeb, 0x63, 0x1f,
0x68, 0xb5, 0x3a, 0x33, 0x6e, 0xea, 0xf1, 0xac, 0x2b, 0xea, 0xdb, 0x47, 0xd9, 0xa7, 0xa8, 0x03, 0x94, 0xaf, 0xea, 0x03, 0x88, 0x59, 0xd2, 0xf2, 0xda, 0x08, 0x36, 0x2e, 0xba, 0x3c, 0x23, 0x37,
0x95, 0x61, 0x20, 0xd5, 0xc7, 0x31, 0x28, 0x5f, 0x35, 0x06, 0x10, 0xb3, 0xa4, 0xe7, 0xb5, 0x11, 0x1f, 0xa7, 0x73, 0xb3, 0xb2, 0xfd, 0xe3, 0xac, 0xfb, 0xb2, 0x45, 0xa6, 0xf2, 0x38, 0x33, 0x6c,
0x6c, 0x5c, 0xa4, 0x3c, 0xa3, 0x36, 0x1f, 0xa7, 0x6b, 0xb3, 0xb2, 0x7d, 0x27, 0x4b, 0x5f, 0xb6, 0xff, 0x61, 0x40, 0xe1, 0x25, 0x75, 0x22, 0x2a, 0xbe, 0x68, 0xd4, 0xee, 0x9c, 0x89, 0xda, 0x7a,
0xc8, 0x54, 0x1d, 0x67, 0xa6, 0xed, 0xdf, 0x0c, 0x28, 0xbc, 0xa4, 0x4e, 0x44, 0xc5, 0x17, 0xcd, 0xf6, 0x20, 0x2c, 0x6f, 0x9d, 0x0b, 0xda, 0x1a, 0x94, 0xbc, 0x40, 0xd0, 0x28, 0x20, 0xbe, 0x8a,
0xda, 0x9d, 0x33, 0x59, 0x5b, 0xcf, 0x7e, 0x08, 0x4b, 0xad, 0x73, 0x49, 0x5b, 0x83, 0x92, 0x17, 0xda, 0x12, 0x9e, 0xee, 0x33, 0x0d, 0xf8, 0x60, 0x40, 0x21, 0x9e, 0x14, 0xaf, 0xdb, 0x80, 0xf8,
0x08, 0x1a, 0x05, 0xc4, 0x57, 0x59, 0x5b, 0xc2, 0xd3, 0x7d, 0xa6, 0x03, 0x1f, 0x0c, 0x28, 0xc4, 0xd6, 0xf3, 0x06, 0x64, 0x2a, 0xf9, 0x3f, 0x03, 0x4a, 0x49, 0xc3, 0xfa, 0xa2, 0x6a, 0x9e, 0x9b,
0x2f, 0xc5, 0xeb, 0x76, 0x20, 0xd6, 0x7a, 0xde, 0x81, 0x4c, 0x23, 0xff, 0x63, 0x40, 0x29, 0x19, 0xbc, 0xf2, 0x5f, 0x7b, 0xf2, 0x42, 0x60, 0xf6, 0xbd, 0x40, 0xcf, 0x88, 0x58, 0xad, 0x51, 0x0b,
0x58, 0x5f, 0xd4, 0xcc, 0x73, 0x2f, 0xaf, 0xfc, 0xff, 0xfd, 0xf2, 0x42, 0x60, 0xf6, 0xbd, 0x40, 0x8a, 0x21, 0x19, 0xfb, 0x8c, 0xb8, 0xba, 0x50, 0xae, 0xcd, 0xfd, 0xb0, 0xd0, 0x0e, 0xc6, 0x38,
0xbf, 0x11, 0xb1, 0x5a, 0xa3, 0x16, 0x14, 0x43, 0x32, 0xf6, 0x19, 0x71, 0x75, 0xa3, 0x5c, 0x9b, 0x01, 0xed, 0xae, 0x1d, 0x9f, 0x34, 0x57, 0xa1, 0x9a, 0xb6, 0xfc, 0x9d, 0xd1, 0xfc, 0xb7, 0x01,
0xfb, 0x61, 0xa1, 0x1d, 0x8c, 0x71, 0x02, 0xda, 0x5d, 0x3b, 0x3e, 0x69, 0xae, 0x42, 0x35, 0xed, 0xe5, 0x83, 0x3f, 0x0a, 0x1a, 0xa8, 0x79, 0xe0, 0x1b, 0x69, 0xfc, 0xe6, 0xfc, 0x8f, 0x0f, 0xe5,
0xf9, 0x3b, 0xa3, 0xf9, 0x4f, 0x03, 0xca, 0x07, 0xbf, 0x17, 0x34, 0x50, 0xef, 0x81, 0xaf, 0xa5, 0x33, 0xbf, 0x2b, 0x64, 0x3d, 0x6a, 0xc7, 0xfa, 0xf8, 0xa9, 0x7e, 0xe3, 0x3f, 0x9f, 0xea, 0x37,
0xf3, 0x9b, 0xf3, 0x3f, 0x3e, 0x94, 0xcf, 0xfc, 0xae, 0x90, 0x75, 0xa9, 0x1d, 0xeb, 0xe3, 0xa7, 0xfe, 0x34, 0xa9, 0x1b, 0x1f, 0x27, 0x75, 0xe3, 0x5f, 0x93, 0xba, 0xf1, 0xdf, 0x49, 0xdd, 0x38,
0xfa, 0x8d, 0x7f, 0x7d, 0xaa, 0xdf, 0xf8, 0xc3, 0xa4, 0x6e, 0x7c, 0x9c, 0xd4, 0x8d, 0x7f, 0x4c, 0x2a, 0x28, 0xff, 0xfc, 0xf4, 0xab, 0x00, 0x00, 0x00, 0xff, 0xff, 0xe4, 0x48, 0xcb, 0x39, 0xc2,
0xea, 0xc6, 0xbf, 0x27, 0x75, 0xe3, 0xa8, 0xa0, 0xe2, 0xf3, 0xa3, 0xff, 0x05, 0x00, 0x00, 0xff, 0x12, 0x00, 0x00,
0xff, 0x34, 0x0b, 0x7d, 0x79, 0x43, 0x13, 0x00, 0x00,
} }

View File

@ -2,12 +2,12 @@ syntax = "proto3";
package docker.swarmkit.v1; package docker.swarmkit.v1;
import "github.com/docker/swarmkit/api/types.proto"; import "types.proto";
import "github.com/docker/swarmkit/api/specs.proto"; import "specs.proto";
import "google/protobuf/timestamp.proto"; import "google/protobuf/timestamp.proto";
import "gogoproto/gogo.proto"; import "gogoproto/gogo.proto";
import "google/protobuf/any.proto"; import "google/protobuf/any.proto";
import "github.com/docker/swarmkit/protobuf/plugin/plugin.proto"; import "plugin/plugin.proto";
// This file contains definitions for all first-class objects in the cluster // This file contains definitions for all first-class objects in the cluster
// API. Such types typically have a corresponding specification, with the // API. Such types typically have a corresponding specification, with the

View File

@ -1,5 +1,5 @@
// Code generated by protoc-gen-gogo. // Code generated by protoc-gen-gogo.
// source: github.com/docker/swarmkit/api/raft.proto // source: raft.proto
// DO NOT EDIT! // DO NOT EDIT!
package api package api
@ -907,7 +907,7 @@ var _Raft_serviceDesc = grpc.ServiceDesc{
}, },
}, },
Streams: []grpc.StreamDesc{}, Streams: []grpc.StreamDesc{},
Metadata: "github.com/docker/swarmkit/api/raft.proto", Metadata: "raft.proto",
} }
// Client API for RaftMembership service // Client API for RaftMembership service
@ -1008,7 +1008,7 @@ var _RaftMembership_serviceDesc = grpc.ServiceDesc{
}, },
}, },
Streams: []grpc.StreamDesc{}, Streams: []grpc.StreamDesc{},
Metadata: "github.com/docker/swarmkit/api/raft.proto", Metadata: "raft.proto",
} }
func (m *RaftMember) Marshal() (dAtA []byte, err error) { func (m *RaftMember) Marshal() (dAtA []byte, err error) {
@ -3571,69 +3571,68 @@ var (
ErrIntOverflowRaft = fmt.Errorf("proto: integer overflow") ErrIntOverflowRaft = fmt.Errorf("proto: integer overflow")
) )
func init() { proto.RegisterFile("github.com/docker/swarmkit/api/raft.proto", fileDescriptorRaft) } func init() { proto.RegisterFile("raft.proto", fileDescriptorRaft) }
var fileDescriptorRaft = []byte{ var fileDescriptorRaft = []byte{
// 974 bytes of a gzipped FileDescriptorProto // 953 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x96, 0x4f, 0x6f, 0x1b, 0x45, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x96, 0x4d, 0x6f, 0x1b, 0x45,
0x18, 0xc6, 0x77, 0xd7, 0x5b, 0x27, 0x79, 0xd3, 0x26, 0xd1, 0x94, 0x84, 0xed, 0x52, 0x1c, 0x77, 0x18, 0xc7, 0x77, 0xd7, 0x5b, 0x27, 0x79, 0xdc, 0xbc, 0x68, 0x42, 0xc2, 0x76, 0x29, 0x8e, 0xbb,
0x8b, 0x84, 0x13, 0x9a, 0xb5, 0x30, 0x48, 0x45, 0x85, 0x1e, 0x62, 0xc7, 0x92, 0x4d, 0x5b, 0xa7, 0x45, 0xc2, 0x2d, 0x64, 0x2d, 0x0c, 0x12, 0xa8, 0xd0, 0x43, 0xec, 0x58, 0xb2, 0x69, 0xeb, 0x54,
0xda, 0x24, 0xd0, 0x5b, 0x58, 0xef, 0x4e, 0xdc, 0xc5, 0xf6, 0x8e, 0x99, 0x19, 0x3b, 0x70, 0x41, 0x9b, 0x04, 0x7a, 0x0b, 0xeb, 0xdd, 0x89, 0xbb, 0xd8, 0xde, 0x31, 0x33, 0x63, 0x07, 0x2e, 0xa8,
0x3d, 0xa2, 0x5c, 0x39, 0x80, 0x90, 0x7a, 0x82, 0x73, 0x3f, 0x00, 0x1f, 0x00, 0x45, 0x9c, 0xb8, 0x47, 0x94, 0x2b, 0x07, 0x10, 0x52, 0x4f, 0x70, 0xee, 0x07, 0xe0, 0x03, 0xa0, 0x88, 0x13, 0x37,
0xc1, 0x29, 0xa2, 0xfe, 0x00, 0xf0, 0x15, 0xd0, 0xcc, 0xee, 0x3a, 0xc6, 0x59, 0x3b, 0xb9, 0x24, 0x38, 0x45, 0xd4, 0x1f, 0x00, 0xbe, 0x02, 0x9a, 0xd9, 0x5d, 0x3b, 0x38, 0x6b, 0x37, 0x17, 0x7b,
0xa3, 0x9d, 0xdf, 0xf3, 0x3e, 0xef, 0x3b, 0x7f, 0xde, 0x31, 0x6c, 0xb4, 0x02, 0xfe, 0xbc, 0xdf, 0x3c, 0xf3, 0xfb, 0x3f, 0xff, 0x79, 0xe6, 0xe5, 0x19, 0x03, 0x50, 0xf7, 0x98, 0xdb, 0x7d, 0x4a,
0xb4, 0x3d, 0xd2, 0x2d, 0xfa, 0xc4, 0x6b, 0x63, 0x5a, 0x64, 0xc7, 0x2e, 0xed, 0xb6, 0x03, 0x5e, 0x38, 0x41, 0xc8, 0x27, 0x5e, 0x07, 0x53, 0x9b, 0x9d, 0xb8, 0xb4, 0xd7, 0x09, 0xb8, 0x3d, 0x7c,
0x74, 0x7b, 0x41, 0x91, 0xba, 0x47, 0xdc, 0xee, 0x51, 0xc2, 0x09, 0x42, 0xd1, 0xbc, 0x9d, 0xcc, 0xcf, 0x5c, 0x26, 0xad, 0x2f, 0xb1, 0xc7, 0x59, 0x84, 0x98, 0x39, 0xfe, 0x4d, 0x1f, 0x27, 0x3f,
0xdb, 0x83, 0xf7, 0xcd, 0x7b, 0x97, 0xc8, 0x49, 0xf3, 0x4b, 0xec, 0x71, 0x16, 0x45, 0x30, 0x37, 0xb6, 0xdb, 0x01, 0x7f, 0x3a, 0x68, 0xd9, 0x1e, 0xe9, 0x95, 0x3c, 0x42, 0x31, 0x61, 0x25, 0xcc,
0x2f, 0xa1, 0xf9, 0x37, 0x3d, 0x9c, 0xb0, 0x5b, 0x63, 0xac, 0x47, 0x28, 0x26, 0xac, 0x88, 0xb9, 0x3d, 0xbf, 0x24, 0x42, 0xca, 0x8f, 0x7e, 0xab, 0x34, 0x09, 0x6f, 0xbe, 0xd6, 0x26, 0x6d, 0x22,
0xe7, 0xcb, 0x84, 0xe4, 0x9f, 0x5e, 0x73, 0x2c, 0x39, 0xf3, 0x8d, 0x16, 0x69, 0x11, 0x39, 0x2c, 0x9b, 0x25, 0xd1, 0x8a, 0x7b, 0xd7, 0xfb, 0xdd, 0x41, 0x3b, 0x08, 0x4b, 0xd1, 0x57, 0xd4, 0x69,
0x8a, 0x51, 0xfc, 0xf5, 0xfe, 0x0c, 0x43, 0x49, 0x34, 0xfb, 0x47, 0xc5, 0x5e, 0xa7, 0xdf, 0x0a, 0xbd, 0x50, 0x01, 0x1c, 0xf7, 0x98, 0x3f, 0xc2, 0xbd, 0x16, 0xa6, 0xe8, 0x36, 0x2c, 0x88, 0x38,
0xc2, 0xf8, 0x5f, 0x24, 0xb4, 0x5e, 0xa9, 0x00, 0x8e, 0x7b, 0xc4, 0x9f, 0xe0, 0x6e, 0x13, 0x53, 0x47, 0x81, 0x6f, 0xa8, 0x05, 0xb5, 0xa8, 0x57, 0x60, 0x74, 0xbe, 0x95, 0x15, 0x40, 0x63, 0xd7,
0x74, 0x17, 0xe6, 0x84, 0xd7, 0x61, 0xe0, 0x1b, 0x6a, 0x5e, 0x2d, 0xe8, 0x65, 0x18, 0x9e, 0xad, 0xc9, 0x8a, 0xa1, 0x86, 0x2f, 0xa0, 0x90, 0xf8, 0x58, 0x40, 0x5a, 0x41, 0x2d, 0x2e, 0x45, 0x50,
0x67, 0x05, 0x50, 0xdf, 0x71, 0xb2, 0x62, 0xaa, 0xee, 0x0b, 0x28, 0x24, 0x3e, 0x16, 0x90, 0x96, 0x93, 0xf8, 0x58, 0x40, 0x62, 0xa8, 0xe1, 0x23, 0x04, 0xba, 0xeb, 0xfb, 0xd4, 0xc8, 0x08, 0xc2,
0x57, 0x0b, 0x0b, 0x11, 0xd4, 0x20, 0x3e, 0x16, 0x90, 0x98, 0xaa, 0xfb, 0x08, 0x81, 0xee, 0xfa, 0x91, 0x6d, 0x54, 0x81, 0x2c, 0xe3, 0x2e, 0x1f, 0x30, 0x43, 0x2f, 0xa8, 0xc5, 0x5c, 0xf9, 0x2d,
0x3e, 0x35, 0x32, 0x82, 0x70, 0xe4, 0x18, 0x95, 0x21, 0xcb, 0xb8, 0xcb, 0xfb, 0xcc, 0xd0, 0xf3, 0xfb, 0xf2, 0x3a, 0xd8, 0x93, 0xd9, 0xec, 0x4b, 0xb6, 0xa2, 0x9f, 0x9d, 0x6f, 0x29, 0x4e, 0xac,
0x6a, 0x61, 0xb1, 0xf4, 0x8e, 0x7d, 0x71, 0xa5, 0xed, 0xf3, 0x6c, 0xf6, 0x24, 0x5b, 0xd6, 0x4f, 0xb4, 0x6e, 0x41, 0xee, 0x53, 0x12, 0x84, 0x0e, 0xfe, 0x6a, 0x80, 0x19, 0x1f, 0xdb, 0xa8, 0x13,
0xcf, 0xd6, 0x15, 0x27, 0x56, 0x5a, 0x77, 0x60, 0xf1, 0x53, 0x12, 0x84, 0x0e, 0xfe, 0xaa, 0x8f, 0x1b, 0xeb, 0x27, 0x15, 0xae, 0x47, 0x0c, 0xeb, 0x93, 0x90, 0xe1, 0xab, 0x65, 0xf5, 0x11, 0x2c,
0x19, 0x1f, 0xd9, 0xa8, 0xe7, 0x36, 0xd6, 0x4f, 0x2a, 0x5c, 0x8f, 0x18, 0xd6, 0x23, 0x21, 0xc3, 0xf4, 0xa4, 0x2d, 0x33, 0xb4, 0x42, 0xa6, 0x98, 0x2b, 0xe7, 0xe7, 0xcf, 0xce, 0x49, 0x70, 0xf4,
0x57, 0xab, 0xea, 0x23, 0x98, 0xeb, 0x4a, 0x5b, 0x66, 0x68, 0xf9, 0x4c, 0x61, 0xb1, 0x94, 0x9b, 0x0e, 0xac, 0x52, 0xdc, 0x23, 0x43, 0xec, 0x1f, 0x25, 0x11, 0x32, 0x85, 0x4c, 0x51, 0xaf, 0x68,
0x9d, 0x9d, 0x93, 0xe0, 0xe8, 0x3d, 0x58, 0xa6, 0xb8, 0x4b, 0x06, 0xd8, 0x3f, 0x4c, 0x22, 0x64, 0x6b, 0x8a, 0xb3, 0x12, 0x0f, 0x45, 0x22, 0x66, 0x55, 0xe0, 0xfa, 0x43, 0xec, 0x0e, 0x71, 0x92,
0xf2, 0x99, 0x82, 0x5e, 0xd6, 0x56, 0x14, 0x67, 0x29, 0x9e, 0x8a, 0x44, 0xcc, 0x2a, 0xc3, 0xf5, 0x40, 0x19, 0x74, 0xb1, 0x62, 0x72, 0x62, 0xaf, 0xf6, 0x94, 0xac, 0xb5, 0x0a, 0xcb, 0x71, 0x8c,
0xc7, 0xd8, 0x1d, 0xe0, 0xa4, 0x80, 0x12, 0xe8, 0x62, 0xc5, 0x64, 0x62, 0x97, 0x7b, 0x4a, 0xd6, 0x28, 0x41, 0xeb, 0x21, 0xdc, 0x78, 0x4c, 0x89, 0x87, 0x19, 0x8b, 0x58, 0xc6, 0xdc, 0xf6, 0xd8,
0x5a, 0x86, 0x1b, 0x71, 0x8c, 0xa8, 0x40, 0xeb, 0x31, 0xdc, 0x7a, 0x4a, 0x89, 0x87, 0x19, 0x8b, 0xe1, 0x8e, 0x48, 0x4c, 0xf6, 0xc4, 0x26, 0xab, 0x76, 0x74, 0x64, 0xec, 0x04, 0x4c, 0xc6, 0xef,
0x58, 0xc6, 0xdc, 0xd6, 0xc8, 0x61, 0x43, 0x14, 0x26, 0xbf, 0xc4, 0x26, 0xcb, 0x76, 0x74, 0xac, 0xe9, 0xcf, 0x7e, 0xb0, 0x14, 0xeb, 0x26, 0x98, 0x69, 0xd1, 0x62, 0xaf, 0x4f, 0x60, 0xc3, 0xc1,
0xec, 0x04, 0x4c, 0xe6, 0x1f, 0xe8, 0x2f, 0x7e, 0xb0, 0x14, 0xeb, 0x36, 0x98, 0x69, 0xd1, 0x62, 0x8c, 0x74, 0x87, 0x78, 0xc7, 0xf7, 0xa9, 0x80, 0x62, 0x9f, 0xab, 0xac, 0xb2, 0xf5, 0x2e, 0x6c,
0xaf, 0x4f, 0x60, 0xd5, 0xc1, 0x8c, 0x74, 0x06, 0x78, 0xdb, 0xf7, 0xa9, 0x80, 0x62, 0x9f, 0xab, 0x4e, 0xab, 0xe3, 0x4d, 0x4a, 0xdb, 0xc9, 0x2e, 0xac, 0x37, 0x42, 0x8e, 0x69, 0xe8, 0x76, 0x45,
0xac, 0xb2, 0x75, 0x0f, 0xd6, 0x26, 0xd5, 0xf1, 0x26, 0xa5, 0xed, 0x64, 0x07, 0x6e, 0xd6, 0x43, 0x9c, 0xc4, 0x69, 0x13, 0xb4, 0xb1, 0x49, 0x76, 0x74, 0xbe, 0xa5, 0x35, 0x76, 0x1d, 0x2d, 0xf0,
0x8e, 0x69, 0xe8, 0x76, 0x44, 0x9c, 0xc4, 0x69, 0x0d, 0xb4, 0x91, 0x49, 0x76, 0x78, 0xb6, 0xae, 0xd1, 0x7d, 0xc8, 0xba, 0x1e, 0x0f, 0x48, 0x18, 0xef, 0xe0, 0x56, 0xda, 0x6a, 0xee, 0x73, 0x42,
0xd5, 0x77, 0x1c, 0x2d, 0xf0, 0xd1, 0x43, 0xc8, 0xba, 0x1e, 0x0f, 0x48, 0x18, 0xef, 0xe0, 0x7a, 0xf1, 0x8e, 0xc4, 0x92, 0xa3, 0x15, 0x89, 0xac, 0xdf, 0x74, 0xc8, 0x5d, 0x18, 0x45, 0x1f, 0x8f,
0xda, 0x6a, 0xee, 0x71, 0x42, 0xf1, 0xb6, 0xc4, 0x92, 0xa3, 0x15, 0x89, 0xac, 0xdf, 0x74, 0x58, 0xc3, 0x09, 0xab, 0x95, 0xf2, 0xed, 0x57, 0x84, 0x7b, 0x10, 0x84, 0x7e, 0x12, 0x0c, 0xd9, 0xf1,
0x1c, 0x9b, 0x45, 0x1f, 0x8f, 0xc2, 0x09, 0xab, 0xa5, 0xd2, 0xdd, 0x4b, 0xc2, 0x3d, 0x0a, 0x42, 0xbe, 0x6a, 0x72, 0xc9, 0x8d, 0x34, 0xa9, 0xb8, 0x31, 0x75, 0x25, 0xda, 0x53, 0xf4, 0x21, 0x2c,
0x3f, 0x09, 0x86, 0xec, 0x78, 0x5f, 0x35, 0xb9, 0xe4, 0x46, 0x9a, 0x54, 0xdc, 0x98, 0x9a, 0x12, 0x30, 0x4c, 0x87, 0x81, 0x87, 0xe5, 0x95, 0xc9, 0x95, 0xdf, 0x48, 0x75, 0x8b, 0x90, 0xba, 0xe2,
0xed, 0x29, 0xba, 0x0f, 0x73, 0x0c, 0xd3, 0x41, 0xe0, 0x61, 0x79, 0x65, 0x16, 0x4b, 0x6f, 0xa5, 0x24, 0xb4, 0x30, 0xe2, 0x2e, 0xeb, 0xc4, 0x57, 0x2a, 0xd5, 0xe8, 0xc0, 0x65, 0x1d, 0x61, 0x24,
0xba, 0x45, 0x48, 0x4d, 0x71, 0x12, 0x5a, 0x18, 0x71, 0x97, 0xb5, 0xe3, 0x2b, 0x95, 0x6a, 0xb4, 0x38, 0x61, 0x14, 0x62, 0x7e, 0x42, 0x68, 0xc7, 0xb8, 0x36, 0xdb, 0xa8, 0x19, 0x21, 0xc2, 0x28,
0xef, 0xb2, 0xb6, 0x30, 0x12, 0x9c, 0x30, 0x0a, 0x31, 0x3f, 0x26, 0xb4, 0x6d, 0x5c, 0x9b, 0x6e, 0xa6, 0x85, 0xd0, 0xeb, 0x0e, 0x18, 0xc7, 0xd4, 0xc8, 0xce, 0x16, 0x56, 0x23, 0x44, 0x08, 0x63,
0xd4, 0x88, 0x10, 0x61, 0x14, 0xd3, 0x42, 0xe8, 0x75, 0xfa, 0x8c, 0x63, 0x6a, 0x64, 0xa7, 0x0b, 0x1a, 0x7d, 0x00, 0x59, 0x86, 0x3d, 0x8a, 0xb9, 0xb1, 0x20, 0x75, 0x66, 0x7a, 0x66, 0x82, 0xa8,
0x2b, 0x11, 0x22, 0x84, 0x31, 0x8d, 0x3e, 0x84, 0x2c, 0xc3, 0x1e, 0xc5, 0xdc, 0x98, 0x93, 0x3a, 0x8b, 0x8b, 0x2e, 0x5b, 0xe8, 0x1e, 0x2c, 0x52, 0xcc, 0xc8, 0x80, 0x7a, 0xd8, 0x58, 0x94, 0xba,
0x33, 0xbd, 0x32, 0x41, 0xd4, 0xc4, 0x45, 0x97, 0x23, 0xf4, 0x00, 0xe6, 0x29, 0x66, 0xa4, 0x4f, 0x9b, 0xa9, 0x97, 0x23, 0x66, 0xea, 0x8a, 0x33, 0xe6, 0xd1, 0x7d, 0x58, 0xc2, 0x5f, 0x73, 0x1c,
0x3d, 0x6c, 0xcc, 0x4b, 0xdd, 0xed, 0xd4, 0xcb, 0x11, 0x33, 0x35, 0xc5, 0x19, 0xf1, 0xe8, 0x21, 0x32, 0xb1, 0x79, 0x4b, 0x52, 0xfc, 0x66, 0x9a, 0xb8, 0x96, 0x40, 0x75, 0xc5, 0x99, 0x28, 0xc4,
0x2c, 0xe0, 0xaf, 0x39, 0x0e, 0x99, 0xd8, 0xbc, 0x05, 0x29, 0x7e, 0x3b, 0x4d, 0x5c, 0x4d, 0xa0, 0x84, 0x3d, 0x12, 0x1e, 0x07, 0x6d, 0x03, 0x66, 0x4f, 0xb8, 0x2a, 0x09, 0x31, 0xe1, 0x88, 0xad,
0x9a, 0xe2, 0x9c, 0x2b, 0x44, 0xc2, 0x1e, 0x09, 0x8f, 0x82, 0x96, 0x01, 0xd3, 0x13, 0xae, 0x48, 0x2c, 0x42, 0x96, 0xbb, 0xb4, 0x8d, 0xf9, 0xdd, 0x7f, 0x55, 0x58, 0x9d, 0x3a, 0x17, 0xe8, 0x6d,
0x42, 0x24, 0x1c, 0xb1, 0xe5, 0x79, 0xc8, 0x72, 0x97, 0xb6, 0x30, 0xdf, 0xfc, 0x57, 0x85, 0xe5, 0x58, 0x38, 0x6c, 0x3e, 0x68, 0xee, 0x7d, 0xde, 0x5c, 0x53, 0x4c, 0xf3, 0xf4, 0x79, 0x61, 0x73,
0x89, 0x73, 0x81, 0xde, 0x85, 0xb9, 0x83, 0xc6, 0xa3, 0xc6, 0xee, 0xe7, 0x8d, 0x15, 0xc5, 0x34, 0x8a, 0x38, 0x0c, 0x3b, 0x21, 0x39, 0x09, 0x51, 0x19, 0xd6, 0xf7, 0x0f, 0xf6, 0x9c, 0xda, 0xd1,
0x4f, 0x5e, 0xe6, 0xd7, 0x26, 0x88, 0x83, 0xb0, 0x1d, 0x92, 0xe3, 0x10, 0x95, 0xe0, 0xe6, 0xde, 0x4e, 0xf5, 0xa0, 0xb1, 0xd7, 0x3c, 0xaa, 0x3a, 0xb5, 0x9d, 0x83, 0xda, 0x9a, 0x6a, 0xde, 0x38,
0xfe, 0xae, 0x53, 0x3d, 0xdc, 0xae, 0xec, 0xd7, 0x77, 0x1b, 0x87, 0x15, 0xa7, 0xba, 0xbd, 0x5f, 0x7d, 0x5e, 0xd8, 0x98, 0x12, 0x55, 0x29, 0x76, 0x39, 0xbe, 0xa4, 0x39, 0x7c, 0xbc, 0x2b, 0x34,
0x5d, 0x51, 0xcd, 0x5b, 0x27, 0x2f, 0xf3, 0xab, 0x13, 0xa2, 0x0a, 0xc5, 0x2e, 0xc7, 0x17, 0x34, 0x5a, 0xaa, 0xe6, 0xb0, 0xef, 0xa7, 0x69, 0x9c, 0xda, 0xa3, 0xbd, 0xcf, 0x6a, 0x6b, 0x99, 0x54,
0x07, 0x4f, 0x77, 0x84, 0x46, 0x4b, 0xd5, 0x1c, 0xf4, 0xfc, 0x34, 0x8d, 0x53, 0x7d, 0xb2, 0xfb, 0x8d, 0x23, 0x8b, 0x98, 0xf9, 0xfa, 0x77, 0x3f, 0xe7, 0x95, 0x5f, 0x7f, 0xc9, 0x4f, 0x67, 0x57,
0x59, 0x75, 0x25, 0x93, 0xaa, 0x71, 0x64, 0x13, 0x33, 0xdf, 0xfc, 0xee, 0xe7, 0x9c, 0xf2, 0xeb, 0xfe, 0x5e, 0x03, 0x5d, 0xdc, 0x50, 0x74, 0xaa, 0x02, 0xba, 0x5c, 0x3c, 0xd0, 0x76, 0xda, 0x0a,
0x2f, 0xb9, 0xc9, 0xea, 0x4a, 0xdf, 0x6b, 0xa0, 0x8b, 0x1b, 0x8a, 0x4e, 0x54, 0x40, 0x17, 0x9b, 0xce, 0x2c, 0x59, 0xa6, 0x7d, 0x55, 0x3c, 0xae, 0x49, 0x1b, 0xbf, 0xbf, 0xf8, 0xe7, 0x47, 0x6d,
0x07, 0xda, 0x4a, 0x5b, 0xc1, 0xa9, 0x2d, 0xcb, 0xb4, 0xaf, 0x8a, 0xc7, 0x3d, 0x69, 0xf5, 0xf7, 0x15, 0x96, 0x25, 0xbf, 0xdd, 0x73, 0x43, 0xb7, 0x8d, 0x29, 0xfa, 0x16, 0x56, 0xfe, 0x5f, 0x6c,
0x57, 0xff, 0xfc, 0xa8, 0x2d, 0xc3, 0x0d, 0xc9, 0x6f, 0x75, 0xdd, 0xd0, 0x6d, 0x61, 0x8a, 0xbe, 0xd0, 0x9d, 0x59, 0x47, 0xe8, 0x52, 0x39, 0x33, 0xef, 0x5e, 0x05, 0x9d, 0xeb, 0x5f, 0xfe, 0x53,
0x85, 0xa5, 0xff, 0x37, 0x1b, 0xb4, 0x31, 0xed, 0x08, 0x5d, 0x68, 0x67, 0xe6, 0xe6, 0x55, 0xd0, 0x85, 0x95, 0x49, 0xf1, 0x66, 0x4f, 0x83, 0x3e, 0xfa, 0x02, 0x74, 0xf1, 0x34, 0xa1, 0xd4, 0xd2,
0x99, 0xfe, 0xa5, 0x3f, 0x55, 0x58, 0x3a, 0x6f, 0xde, 0xec, 0x79, 0xd0, 0x43, 0x5f, 0x80, 0x2e, 0x74, 0xe1, 0x61, 0x33, 0x0b, 0xb3, 0x81, 0xf9, 0x49, 0x7b, 0x70, 0x4d, 0x3e, 0x0e, 0x28, 0x35,
0x9e, 0x26, 0x94, 0xda, 0x9a, 0xc6, 0x1e, 0x36, 0x33, 0x3f, 0x1d, 0x98, 0x5d, 0xb4, 0x07, 0xd7, 0xc2, 0xc5, 0xb7, 0xc7, 0xbc, 0x35, 0x87, 0x98, 0x6b, 0x52, 0x31, 0xce, 0x5e, 0xe6, 0x95, 0xbf,
0xe4, 0xe3, 0x80, 0x52, 0x23, 0x8c, 0xbf, 0x3d, 0xe6, 0x9d, 0x19, 0xc4, 0x4c, 0x93, 0xb2, 0x71, 0x5e, 0xe6, 0x95, 0x67, 0xa3, 0xbc, 0x7a, 0x36, 0xca, 0xab, 0x7f, 0x8c, 0xf2, 0xea, 0xdf, 0xa3,
0xfa, 0x3a, 0xa7, 0xfc, 0xf5, 0x3a, 0xa7, 0xbc, 0x18, 0xe6, 0xd4, 0xd3, 0x61, 0x4e, 0xfd, 0x63, 0xbc, 0xfa, 0x24, 0xf3, 0x44, 0x6f, 0x65, 0xe5, 0x7f, 0x8b, 0xf7, 0xff, 0x0b, 0x00, 0x00, 0xff,
0x98, 0x53, 0xff, 0x1e, 0xe6, 0xd4, 0x67, 0x99, 0x67, 0x7a, 0x33, 0x2b, 0x7f, 0x5b, 0x7c, 0xf0, 0xff, 0x9a, 0xef, 0x6e, 0xdb, 0xf3, 0x08, 0x00, 0x00,
0x5f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x64, 0x01, 0xa3, 0x4f, 0x74, 0x09, 0x00, 0x00,
} }

View File

@ -2,11 +2,11 @@ syntax = "proto3";
package docker.swarmkit.v1; package docker.swarmkit.v1;
import "github.com/docker/swarmkit/api/objects.proto"; import "objects.proto";
import "github.com/docker/swarmkit/api/types.proto"; import "types.proto";
import "github.com/coreos/etcd/raft/raftpb/raft.proto"; import "github.com/coreos/etcd/raft/raftpb/raft.proto";
import weak "gogoproto/gogo.proto"; import weak "gogoproto/gogo.proto";
import weak "github.com/docker/swarmkit/protobuf/plugin/plugin.proto"; import weak "plugin/plugin.proto";
// Raft defines the RPC communication between raft nodes. // Raft defines the RPC communication between raft nodes.
service Raft { service Raft {

View File

@ -1,5 +1,5 @@
// Code generated by protoc-gen-gogo. // Code generated by protoc-gen-gogo.
// source: github.com/docker/swarmkit/api/resource.proto // source: resource.proto
// DO NOT EDIT! // DO NOT EDIT!
package api package api
@ -262,7 +262,7 @@ var _ResourceAllocator_serviceDesc = grpc.ServiceDesc{
}, },
}, },
Streams: []grpc.StreamDesc{}, Streams: []grpc.StreamDesc{},
Metadata: "github.com/docker/swarmkit/api/resource.proto", Metadata: "resource.proto",
} }
func (m *AttachNetworkRequest) Marshal() (dAtA []byte, err error) { func (m *AttachNetworkRequest) Marshal() (dAtA []byte, err error) {
@ -1061,35 +1061,31 @@ var (
ErrIntOverflowResource = fmt.Errorf("proto: integer overflow") ErrIntOverflowResource = fmt.Errorf("proto: integer overflow")
) )
func init() { func init() { proto.RegisterFile("resource.proto", fileDescriptorResource) }
proto.RegisterFile("github.com/docker/swarmkit/api/resource.proto", fileDescriptorResource)
}
var fileDescriptorResource = []byte{ var fileDescriptorResource = []byte{
// 397 bytes of a gzipped FileDescriptorProto // 368 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x92, 0xcf, 0x4e, 0xf2, 0x40, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x2b, 0x4a, 0x2d, 0xce,
0x14, 0xc5, 0x19, 0x16, 0x24, 0xdf, 0x50, 0xf2, 0x69, 0x03, 0x91, 0x90, 0x58, 0x48, 0xdd, 0xa0, 0x2f, 0x2d, 0x4a, 0x4e, 0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x4a, 0xc9, 0x4f, 0xce,
0x86, 0x36, 0x62, 0x8c, 0x6b, 0xfe, 0x6c, 0xba, 0x90, 0x45, 0x5f, 0xc0, 0x0c, 0xed, 0x50, 0x1a, 0x4e, 0x2d, 0xd2, 0x2b, 0x2e, 0x4f, 0x2c, 0xca, 0xcd, 0xce, 0x2c, 0xd1, 0x2b, 0x33, 0x94, 0xe2,
0x68, 0xa7, 0x4e, 0xa7, 0x12, 0x77, 0x6e, 0x5d, 0xb9, 0xf5, 0x1d, 0x4c, 0x7c, 0x0e, 0xe2, 0xca, 0x2e, 0xa9, 0x2c, 0x48, 0x2d, 0x86, 0x28, 0x90, 0x12, 0x49, 0xcf, 0x4f, 0xcf, 0x07, 0x33, 0xf5,
0xa5, 0x2b, 0x22, 0x7d, 0x00, 0x9f, 0xc1, 0xd0, 0x29, 0x10, 0x70, 0xa2, 0xc4, 0x55, 0xa7, 0xd3, 0x41, 0x2c, 0xa8, 0xa8, 0x70, 0x41, 0x4e, 0x69, 0x7a, 0x66, 0x9e, 0x3e, 0x84, 0x82, 0x08, 0x2a,
0x73, 0xce, 0xfd, 0xdd, 0x7b, 0x0b, 0x1b, 0x8e, 0xcb, 0x86, 0x51, 0x5f, 0xb3, 0x88, 0xa7, 0xdb, 0xf5, 0x33, 0x72, 0x89, 0x38, 0x96, 0x94, 0x24, 0x26, 0x67, 0xf8, 0xa5, 0x96, 0x94, 0xe7, 0x17,
0xc4, 0x1a, 0x61, 0xaa, 0x87, 0x13, 0x44, 0xbd, 0x91, 0xcb, 0x74, 0x14, 0xb8, 0x3a, 0xc5, 0x21, 0x65, 0x07, 0xa5, 0x16, 0x96, 0xa6, 0x16, 0x97, 0x08, 0x39, 0x73, 0xb1, 0x25, 0xe7, 0xe7, 0xa5,
0x89, 0xa8, 0x85, 0xb5, 0x80, 0x12, 0x46, 0x64, 0x99, 0x6b, 0xb4, 0xa5, 0x46, 0xbb, 0x3d, 0xab, 0x65, 0xa6, 0x4b, 0x30, 0x2a, 0x30, 0x6a, 0x70, 0x1b, 0x69, 0xeb, 0x61, 0xda, 0xaa, 0x07, 0xd5,
0x9c, 0xfc, 0x12, 0xc1, 0xee, 0x02, 0x1c, 0x72, 0x7f, 0xa5, 0xe8, 0x10, 0x87, 0x24, 0x47, 0x7d, 0x03, 0x31, 0x20, 0x37, 0x35, 0xaf, 0xc4, 0x19, 0xac, 0x25, 0x08, 0xaa, 0x55, 0xc8, 0x88, 0x8b,
0x71, 0x4a, 0x6f, 0x2f, 0x7f, 0x48, 0x48, 0x14, 0xfd, 0x68, 0xa0, 0x07, 0xe3, 0xc8, 0x71, 0xfd, 0x27, 0x39, 0x3f, 0xaf, 0x24, 0x31, 0x33, 0x2f, 0xb5, 0x28, 0x3e, 0x33, 0x45, 0x82, 0x49, 0x81,
0xf4, 0xc1, 0x8d, 0xea, 0x23, 0x80, 0xc5, 0x16, 0x63, 0xc8, 0x1a, 0xf6, 0x30, 0x9b, 0x10, 0x3a, 0x51, 0x83, 0xd3, 0x89, 0xff, 0xd1, 0x3d, 0x79, 0x6e, 0x67, 0x98, 0xb8, 0xa7, 0x4b, 0x10, 0x37,
0x32, 0xf1, 0x4d, 0x84, 0x43, 0x26, 0x77, 0x60, 0xce, 0x22, 0xfe, 0xc0, 0x75, 0xca, 0xa0, 0x06, 0x5c, 0x91, 0x67, 0x8a, 0x92, 0x1f, 0x97, 0x28, 0x9a, 0x83, 0x8a, 0x0b, 0xf2, 0xf3, 0x8a, 0x53,
0xea, 0xf9, 0xe6, 0xa9, 0xf6, 0x1d, 0x5c, 0x4b, 0x3d, 0x3c, 0xc0, 0xc3, 0x3e, 0xeb, 0x24, 0x16, 0x85, 0x4c, 0xb9, 0x78, 0x13, 0xe1, 0x16, 0x81, 0x4c, 0x63, 0x04, 0x9b, 0x26, 0xf0, 0xe8, 0x9e,
0x33, 0xb5, 0xca, 0x4d, 0x28, 0x59, 0xc4, 0x67, 0xc8, 0xf5, 0x31, 0xbd, 0x76, 0xed, 0x72, 0xb6, 0x3c, 0x0f, 0xc2, 0x05, 0x9e, 0x2e, 0x41, 0x3c, 0x08, 0x65, 0x9e, 0x29, 0x4a, 0xbe, 0x5c, 0x22,
0x06, 0xea, 0xff, 0xda, 0xff, 0xe3, 0x59, 0x35, 0xdf, 0x59, 0xde, 0x1b, 0x5d, 0x33, 0xbf, 0x12, 0x2e, 0xa9, 0x58, 0x3c, 0x48, 0xa6, 0x71, 0xe2, 0x5c, 0xa2, 0x68, 0xc6, 0x41, 0x9c, 0x67, 0xb4,
0x19, 0xb6, 0xda, 0x83, 0xa5, 0x2d, 0xa0, 0x30, 0x20, 0x7e, 0x88, 0xe5, 0x0b, 0x58, 0x40, 0xab, 0x9a, 0x89, 0x4b, 0x30, 0x08, 0x1a, 0x51, 0x8e, 0x39, 0x39, 0xf9, 0xc9, 0x89, 0x25, 0xf9, 0x45,
0x42, 0x8b, 0x34, 0x90, 0xa4, 0xed, 0xc5, 0xb3, 0xaa, 0xb4, 0x26, 0x30, 0xba, 0xa6, 0xb4, 0x96, 0x42, 0x9d, 0x8c, 0x5c, 0xbc, 0x28, 0xde, 0x11, 0xd2, 0xc0, 0x16, 0x90, 0xd8, 0xa2, 0x40, 0x4a,
0x19, 0xb6, 0x7a, 0x05, 0x8b, 0x5d, 0x2c, 0x68, 0xf0, 0x8f, 0x71, 0x07, 0xb0, 0xb4, 0x15, 0xc7, 0x93, 0x08, 0x95, 0x10, 0xcb, 0x95, 0x94, 0x4f, 0xad, 0x7b, 0x37, 0x83, 0x49, 0x96, 0x8b, 0x07,
0xf1, 0x9a, 0xcf, 0x59, 0xb8, 0x6f, 0xa6, 0xbb, 0x6e, 0x8d, 0xc7, 0xc4, 0x42, 0x8c, 0x50, 0xf9, 0xac, 0x54, 0x17, 0x24, 0x97, 0x5a, 0xc4, 0xc5, 0x0b, 0xe1, 0xe5, 0x26, 0xe6, 0x25, 0xa6, 0xa7,
0x01, 0xc0, 0xc2, 0x46, 0x3b, 0x72, 0x5d, 0x34, 0x48, 0xd1, 0x0a, 0x2a, 0xc7, 0x3b, 0x28, 0x79, 0x42, 0xdc, 0x82, 0xe2, 0x76, 0xec, 0x6e, 0xc1, 0x16, 0x5a, 0xd8, 0xdd, 0x82, 0x35, 0x20, 0x88,
0x71, 0xf5, 0xe8, 0xf5, 0xe5, 0xf3, 0x29, 0x7b, 0x08, 0xa5, 0x44, 0xda, 0x58, 0x7c, 0xc3, 0x14, 0x72, 0x8b, 0x93, 0xc4, 0x89, 0x87, 0x72, 0x0c, 0x37, 0x1e, 0xca, 0x31, 0x34, 0x3c, 0x92, 0x63,
0x16, 0xf8, 0x9b, 0x87, 0x7c, 0xe4, 0x60, 0xce, 0xb2, 0xc1, 0x2e, 0x66, 0x11, 0x4d, 0x4b, 0xcc, 0x3c, 0xf1, 0x48, 0x8e, 0xf1, 0xc2, 0x23, 0x39, 0xc6, 0x07, 0x8f, 0xe4, 0x18, 0x93, 0xd8, 0xc0,
0x22, 0x1c, 0xc4, 0x4e, 0x2c, 0xed, 0xf2, 0x74, 0xae, 0x64, 0xde, 0xe7, 0x4a, 0xe6, 0x3e, 0x56, 0x09, 0xd3, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0x1c, 0x48, 0x12, 0x41, 0xf6, 0x02, 0x00, 0x00,
0xc0, 0x34, 0x56, 0xc0, 0x5b, 0xac, 0x80, 0x8f, 0x58, 0x01, 0xfd, 0x5c, 0xf2, 0x63, 0x9e, 0x7f,
0x05, 0x00, 0x00, 0xff, 0xff, 0xc1, 0x7a, 0x29, 0xfc, 0x58, 0x03, 0x00, 0x00,
} }

View File

@ -2,9 +2,9 @@ syntax = "proto3";
package docker.swarmkit.v1; package docker.swarmkit.v1;
import "github.com/docker/swarmkit/api/types.proto"; import "types.proto";
import "gogoproto/gogo.proto"; import "gogoproto/gogo.proto";
import "github.com/docker/swarmkit/protobuf/plugin/plugin.proto"; import "plugin/plugin.proto";
// Allocator is the API provided by a manager group for agents to control the allocation of certain entities. // Allocator is the API provided by a manager group for agents to control the allocation of certain entities.
// //

View File

@ -1,5 +1,5 @@
// Code generated by protoc-gen-gogo. // Code generated by protoc-gen-gogo.
// source: github.com/docker/swarmkit/api/snapshot.proto // source: snapshot.proto
// DO NOT EDIT! // DO NOT EDIT!
package api package api
@ -1305,41 +1305,38 @@ var (
ErrIntOverflowSnapshot = fmt.Errorf("proto: integer overflow") ErrIntOverflowSnapshot = fmt.Errorf("proto: integer overflow")
) )
func init() { func init() { proto.RegisterFile("snapshot.proto", fileDescriptorSnapshot) }
proto.RegisterFile("github.com/docker/swarmkit/api/snapshot.proto", fileDescriptorSnapshot)
}
var fileDescriptorSnapshot = []byte{ var fileDescriptorSnapshot = []byte{
// 493 bytes of a gzipped FileDescriptorProto // 469 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x93, 0xbf, 0x8f, 0xd3, 0x30, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x93, 0xbf, 0x6f, 0xd3, 0x40,
0x14, 0xc7, 0x93, 0xf4, 0x47, 0x7a, 0x3e, 0x01, 0x87, 0xc5, 0x60, 0x95, 0x23, 0x94, 0xc2, 0x50, 0x14, 0xc7, 0x7d, 0xce, 0x0f, 0xa7, 0xaf, 0x6a, 0x29, 0x27, 0x86, 0x53, 0x28, 0x26, 0x04, 0x86,
0x24, 0x48, 0xa0, 0x20, 0x81, 0x90, 0x8e, 0xa1, 0x27, 0x06, 0x06, 0x6e, 0x70, 0xd1, 0x89, 0x35, 0x4c, 0x06, 0x02, 0x12, 0x08, 0xa9, 0x0c, 0xa9, 0x18, 0x18, 0xe8, 0x70, 0x41, 0x15, 0xab, 0xe3,
0x4d, 0xdd, 0x36, 0x84, 0xc4, 0x95, 0x9f, 0xdb, 0x63, 0x84, 0xff, 0xae, 0x23, 0x23, 0x13, 0xe2, 0x5c, 0x52, 0x63, 0xe2, 0x8b, 0xee, 0x5d, 0x53, 0x46, 0xf8, 0xef, 0x32, 0x32, 0x32, 0x21, 0x92,
0xba, 0xf0, 0x6f, 0x20, 0xdb, 0x71, 0xa8, 0x44, 0x7a, 0xb7, 0x45, 0xd6, 0xe7, 0xf3, 0xde, 0xd7, 0x85, 0x7f, 0x03, 0xdd, 0x9d, 0x6d, 0x22, 0xe1, 0x74, 0xb3, 0x4e, 0x9f, 0xcf, 0x7b, 0xdf, 0x3b,
0xce, 0x7b, 0xe8, 0xe9, 0x3c, 0x95, 0x8b, 0xd5, 0x24, 0x4c, 0x78, 0x1e, 0x4d, 0x79, 0x92, 0x31, 0xbf, 0x07, 0xc7, 0x98, 0xc7, 0x4b, 0xbc, 0x92, 0x3a, 0x5a, 0x2a, 0xa9, 0x25, 0xa5, 0x53, 0x99,
0x11, 0xc1, 0x45, 0x2c, 0xf2, 0x2c, 0x95, 0x51, 0xbc, 0x4c, 0x23, 0x28, 0xe2, 0x25, 0x2c, 0xb8, 0x64, 0x42, 0x45, 0x78, 0x13, 0xab, 0x45, 0x96, 0xea, 0x68, 0xf5, 0xbc, 0x7b, 0x24, 0x27, 0x9f,
0x0c, 0x97, 0x82, 0x4b, 0x8e, 0xb1, 0x61, 0x42, 0xcb, 0x84, 0xeb, 0xe7, 0xdd, 0x27, 0xd7, 0x94, 0x45, 0xa2, 0xd1, 0x21, 0x5d, 0x50, 0xf1, 0xac, 0xc0, 0xbb, 0xf7, 0xe6, 0x72, 0x2e, 0xed, 0xe7,
0xe0, 0x93, 0xcf, 0x2c, 0x91, 0x60, 0x2a, 0x74, 0x1f, 0x5f, 0x43, 0x8b, 0x78, 0x56, 0x36, 0xeb, 0x53, 0xf3, 0xe5, 0x4e, 0xfb, 0xdf, 0x9b, 0x70, 0x34, 0xd6, 0x52, 0x89, 0x71, 0x51, 0x9c, 0x46,
0xde, 0x99, 0xf3, 0x39, 0xd7, 0x9f, 0x91, 0xfa, 0x32, 0xa7, 0xfd, 0xef, 0x4d, 0x74, 0x63, 0x2c, 0xd0, 0xca, 0xe5, 0x54, 0x20, 0x23, 0xbd, 0xc6, 0xe0, 0x70, 0xc8, 0xa2, 0xff, 0xdb, 0x44, 0x17,
0xb9, 0x60, 0xe3, 0x32, 0x1a, 0x0e, 0x51, 0xab, 0xe0, 0x53, 0x06, 0xc4, 0xed, 0x35, 0x06, 0x87, 0x72, 0x2a, 0xb8, 0xc3, 0xe8, 0x2b, 0xe8, 0xa0, 0x50, 0xab, 0x34, 0x11, 0xc8, 0x7c, 0xab, 0xdc,
0x43, 0x12, 0xfe, 0x1f, 0x32, 0x3c, 0xe3, 0x53, 0x46, 0x0d, 0x86, 0x5f, 0xa1, 0x0e, 0x30, 0xb1, 0xaf, 0x53, 0xc6, 0x8e, 0xe1, 0x15, 0x6c, 0xc4, 0x5c, 0xe8, 0x1b, 0xa9, 0x32, 0x64, 0x8d, 0xfd,
0x4e, 0x13, 0x06, 0xc4, 0xd3, 0xca, 0xdd, 0x3a, 0x65, 0x6c, 0x18, 0x5a, 0xc1, 0x4a, 0x2c, 0x98, 0xe2, 0x85, 0x63, 0x78, 0x05, 0x9b, 0x84, 0x3a, 0xc6, 0x0c, 0x59, 0x73, 0x7f, 0xc2, 0x8f, 0x31,
0xbc, 0xe0, 0x22, 0x03, 0xd2, 0xd8, 0x2f, 0x9e, 0x19, 0x86, 0x56, 0xb0, 0x4a, 0x28, 0x63, 0xc8, 0x66, 0xdc, 0x61, 0xa6, 0x51, 0xf2, 0xe5, 0x1a, 0xb5, 0x50, 0xc8, 0x5a, 0xfb, 0x1b, 0x9d, 0x3b,
0x80, 0x34, 0xf7, 0x27, 0xfc, 0x18, 0x43, 0x46, 0x0d, 0xa6, 0x1a, 0x25, 0x5f, 0x56, 0x20, 0x99, 0x86, 0x57, 0x30, 0x7d, 0x09, 0x01, 0x8a, 0x44, 0x09, 0x8d, 0xac, 0x6d, 0xbd, 0x6e, 0xfd, 0xcd,
0x00, 0xd2, 0xda, 0xdf, 0xe8, 0xd4, 0x30, 0xb4, 0x82, 0xf1, 0x4b, 0xe4, 0x03, 0x4b, 0x04, 0x93, 0x0c, 0xc2, 0x4b, 0x94, 0xbe, 0x81, 0x03, 0x25, 0x50, 0x5e, 0x2b, 0xf3, 0x22, 0x81, 0xf5, 0x4e,
0x40, 0xda, 0xda, 0xeb, 0xd6, 0xdf, 0x4c, 0x21, 0xd4, 0xa2, 0xf8, 0x0d, 0x3a, 0x10, 0x0c, 0xf8, 0xeb, 0x3c, 0x5e, 0x40, 0xfc, 0x1f, 0x4e, 0xcf, 0x00, 0xc4, 0x57, 0x2d, 0x72, 0x4c, 0x65, 0x8e,
0x4a, 0xa8, 0x17, 0xf1, 0xb5, 0x77, 0x5c, 0xe7, 0xd1, 0x12, 0xa2, 0xff, 0x70, 0x7c, 0x82, 0x10, 0xac, 0x63, 0xe5, 0x07, 0x75, 0xf2, 0xbb, 0x92, 0xe2, 0x3b, 0x82, 0x09, 0x9c, 0xc8, 0x7c, 0x96,
0xfb, 0x2a, 0x59, 0x01, 0x29, 0x2f, 0x80, 0x74, 0xb4, 0x7c, 0xaf, 0x4e, 0x7e, 0x67, 0x29, 0xba, 0xce, 0x91, 0x1d, 0xec, 0x0f, 0x7c, 0x6e, 0x11, 0x5e, 0xa2, 0xfd, 0x14, 0xee, 0x14, 0x77, 0xaf,
0x23, 0xa8, 0xc0, 0x09, 0x2f, 0x66, 0xe9, 0x1c, 0xc8, 0xc1, 0xfe, 0xc0, 0xa7, 0x1a, 0xa1, 0x16, 0x86, 0xe0, 0x35, 0x04, 0x0b, 0xb1, 0x98, 0x98, 0x17, 0x73, 0x63, 0x10, 0xd6, 0xde, 0x20, 0x9e,
0xed, 0xa7, 0xe8, 0x56, 0x79, 0xf7, 0x6a, 0x08, 0x5e, 0x23, 0x3f, 0x67, 0xf9, 0x44, 0xbd, 0x98, 0xe9, 0x0f, 0x16, 0xe3, 0x25, 0x4e, 0x4f, 0x21, 0x50, 0x62, 0x21, 0x57, 0x62, 0x6a, 0xa7, 0xa1,
0x19, 0x83, 0xa0, 0xf6, 0x06, 0xf1, 0x4c, 0x7e, 0xd0, 0x18, 0xb5, 0x38, 0x3e, 0x46, 0xbe, 0x60, 0x39, 0xf2, 0x4f, 0x3c, 0x5e, 0x1e, 0xf5, 0xff, 0x10, 0xe8, 0x54, 0x4d, 0xde, 0x42, 0xb0, 0x12,
0x39, 0x5f, 0xb3, 0xa9, 0x9e, 0x86, 0xe6, 0xc8, 0x3b, 0x72, 0xa8, 0x3d, 0xea, 0xff, 0x71, 0x51, 0xca, 0x24, 0x67, 0xa4, 0x47, 0x06, 0xc7, 0xc3, 0x27, 0xb5, 0xcf, 0x5b, 0x4e, 0xfd, 0xa5, 0x63,
0xa7, 0x6a, 0xf2, 0x16, 0xf9, 0x6b, 0x26, 0x54, 0x72, 0xe2, 0xf6, 0xdc, 0xc1, 0xcd, 0xe1, 0xa3, 0x79, 0x29, 0xd1, 0xf7, 0x00, 0x45, 0xd7, 0xab, 0x74, 0xc9, 0xfc, 0x1e, 0x19, 0x1c, 0x0e, 0x1f,
0xda, 0xe7, 0xb5, 0x3b, 0x73, 0x6e, 0x58, 0x6a, 0x25, 0xfc, 0x1e, 0xa1, 0xb2, 0xeb, 0x22, 0x5d, 0xdf, 0xf2, 0x67, 0xcb, 0x4a, 0xa3, 0xe6, 0xfa, 0xd7, 0x43, 0x8f, 0xef, 0xc8, 0xf4, 0x0c, 0x5a,
0x12, 0xaf, 0xe7, 0x0e, 0x0e, 0x87, 0x0f, 0xaf, 0xf8, 0xb3, 0xb6, 0xd2, 0xa8, 0xb9, 0xf9, 0x75, 0x68, 0xb6, 0x80, 0x35, 0x6c, 0x95, 0x47, 0xb5, 0x41, 0x76, 0xd7, 0xa4, 0xa8, 0xe1, 0xac, 0xfe,
0xdf, 0xa1, 0x3b, 0x32, 0x3e, 0x41, 0x2d, 0x50, 0x5b, 0x40, 0x1a, 0xba, 0xca, 0x83, 0xda, 0x20, 0x5d, 0x08, 0x8a, 0x74, 0xb4, 0x0d, 0xfe, 0xe5, 0xb3, 0x13, 0x6f, 0xc4, 0xd6, 0x9b, 0xd0, 0xfb,
0xbb, 0x6b, 0x52, 0xd6, 0x30, 0x56, 0xff, 0x36, 0xf2, 0xcb, 0x74, 0xb8, 0x8d, 0xbc, 0xf3, 0x67, 0xb9, 0x09, 0xbd, 0x6f, 0xdb, 0x90, 0xac, 0xb7, 0x21, 0xf9, 0xb1, 0x0d, 0xc9, 0xef, 0x6d, 0x48,
0x47, 0xce, 0x88, 0x6c, 0x2e, 0x03, 0xe7, 0xe7, 0x65, 0xe0, 0x7c, 0xdb, 0x06, 0xee, 0x66, 0x1b, 0x3e, 0xf9, 0x93, 0xb6, 0xdd, 0xbd, 0x17, 0x7f, 0x03, 0x00, 0x00, 0xff, 0xff, 0x8d, 0x0f, 0xc4,
0xb8, 0x3f, 0xb6, 0x81, 0xfb, 0x7b, 0x1b, 0xb8, 0x9f, 0xbc, 0x49, 0x5b, 0xef, 0xde, 0x8b, 0xbf, 0x6e, 0xd2, 0x03, 0x00, 0x00,
0x01, 0x00, 0x00, 0xff, 0xff, 0xfd, 0xbe, 0x47, 0xec, 0x2f, 0x04, 0x00, 0x00,
} }

Some files were not shown because too many files have changed in this diff Show More