This fix tries to address the issue in 28884 where
it is possible to mask the secret ID by name.
The reason was that searching a secret is based on name.
However, searching a secret should be done based on:
- Full ID
- Full Name
- Partial ID (prefix)
This fix addresses the issue by changing related implementation
in `getCliRequestedSecretIDs()`
An integration test has been added to cover the changes.
This fix fixes 28884
Signed-off-by: Yong Tang <yong.tang.github@outlook.com>
| ... | ... |
@@ -1,6 +1,9 @@ |
| 1 | 1 |
package secret |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
+ "fmt" |
|
| 5 |
+ "strings" |
|
| 6 |
+ |
|
| 4 | 7 |
"github.com/docker/docker/api/types" |
| 5 | 8 |
"github.com/docker/docker/api/types/filters" |
| 6 | 9 |
"github.com/docker/docker/api/types/swarm" |
| ... | ... |
@@ -8,10 +11,11 @@ import ( |
| 8 | 8 |
"golang.org/x/net/context" |
| 9 | 9 |
) |
| 10 | 10 |
|
| 11 |
-func getSecretsByName(ctx context.Context, client client.APIClient, names []string) ([]swarm.Secret, error) {
|
|
| 11 |
+func getSecretsByNameOrIDPrefixes(ctx context.Context, client client.APIClient, terms []string) ([]swarm.Secret, error) {
|
|
| 12 | 12 |
args := filters.NewArgs() |
| 13 |
- for _, n := range names {
|
|
| 13 |
+ for _, n := range terms {
|
|
| 14 | 14 |
args.Add("names", n)
|
| 15 |
+ args.Add("id", n)
|
|
| 15 | 16 |
} |
| 16 | 17 |
|
| 17 | 18 |
return client.SecretList(ctx, types.SecretListOptions{
|
| ... | ... |
@@ -19,29 +23,53 @@ func getSecretsByName(ctx context.Context, client client.APIClient, names []stri |
| 19 | 19 |
}) |
| 20 | 20 |
} |
| 21 | 21 |
|
| 22 |
-func getCliRequestedSecretIDs(ctx context.Context, client client.APIClient, names []string) ([]string, error) {
|
|
| 23 |
- ids := names |
|
| 24 |
- |
|
| 25 |
- // attempt to lookup secret by name |
|
| 26 |
- secrets, err := getSecretsByName(ctx, client, ids) |
|
| 22 |
+func getCliRequestedSecretIDs(ctx context.Context, client client.APIClient, terms []string) ([]string, error) {
|
|
| 23 |
+ secrets, err := getSecretsByNameOrIDPrefixes(ctx, client, terms) |
|
| 27 | 24 |
if err != nil {
|
| 28 | 25 |
return nil, err |
| 29 | 26 |
} |
| 30 | 27 |
|
| 31 |
- lookup := make(map[string]struct{})
|
|
| 32 |
- for _, id := range ids {
|
|
| 33 |
- lookup[id] = struct{}{}
|
|
| 34 |
- } |
|
| 35 |
- |
|
| 36 | 28 |
if len(secrets) > 0 {
|
| 37 |
- ids = []string{}
|
|
| 38 |
- |
|
| 39 |
- for _, s := range secrets {
|
|
| 40 |
- if _, ok := lookup[s.Spec.Annotations.Name]; ok {
|
|
| 41 |
- ids = append(ids, s.ID) |
|
| 29 |
+ found := make(map[string]struct{})
|
|
| 30 |
+ next: |
|
| 31 |
+ for _, term := range terms {
|
|
| 32 |
+ // attempt to lookup secret by full ID |
|
| 33 |
+ for _, s := range secrets {
|
|
| 34 |
+ if s.ID == term {
|
|
| 35 |
+ found[s.ID] = struct{}{}
|
|
| 36 |
+ continue next |
|
| 37 |
+ } |
|
| 38 |
+ } |
|
| 39 |
+ // attempt to lookup secret by full name |
|
| 40 |
+ for _, s := range secrets {
|
|
| 41 |
+ if s.Spec.Annotations.Name == term {
|
|
| 42 |
+ found[s.ID] = struct{}{}
|
|
| 43 |
+ continue next |
|
| 44 |
+ } |
|
| 45 |
+ } |
|
| 46 |
+ // attempt to lookup secret by partial ID (prefix) |
|
| 47 |
+ // return error if more than one matches found (ambiguous) |
|
| 48 |
+ n := 0 |
|
| 49 |
+ for _, s := range secrets {
|
|
| 50 |
+ if strings.HasPrefix(s.ID, term) {
|
|
| 51 |
+ found[s.ID] = struct{}{}
|
|
| 52 |
+ n++ |
|
| 53 |
+ } |
|
| 54 |
+ } |
|
| 55 |
+ if n > 1 {
|
|
| 56 |
+ return nil, fmt.Errorf("secret %s is ambiguous (%d matches found)", term, n)
|
|
| 42 | 57 |
} |
| 43 | 58 |
} |
| 59 |
+ |
|
| 60 |
+ // We already collected all the IDs found. |
|
| 61 |
+ // Now we will remove duplicates by converting the map to slice |
|
| 62 |
+ ids := []string{}
|
|
| 63 |
+ for id := range found {
|
|
| 64 |
+ ids = append(ids, id) |
|
| 65 |
+ } |
|
| 66 |
+ |
|
| 67 |
+ return ids, nil |
|
| 44 | 68 |
} |
| 45 | 69 |
|
| 46 |
- return ids, nil |
|
| 70 |
+ return terms, nil |
|
| 47 | 71 |
} |
| ... | ... |
@@ -46,3 +46,61 @@ func (s *DockerSwarmSuite) TestSecretCreateWithLabels(c *check.C) {
|
| 46 | 46 |
c.Assert(secret.Spec.Labels["key1"], checker.Equals, "value1") |
| 47 | 47 |
c.Assert(secret.Spec.Labels["key2"], checker.Equals, "value2") |
| 48 | 48 |
} |
| 49 |
+ |
|
| 50 |
+// Test case for 28884 |
|
| 51 |
+func (s *DockerSwarmSuite) TestSecretCreateResolve(c *check.C) {
|
|
| 52 |
+ d := s.AddDaemon(c, true, true) |
|
| 53 |
+ |
|
| 54 |
+ name := "foo" |
|
| 55 |
+ id := d.createSecret(c, swarm.SecretSpec{
|
|
| 56 |
+ swarm.Annotations{
|
|
| 57 |
+ Name: name, |
|
| 58 |
+ }, |
|
| 59 |
+ []byte("foo"),
|
|
| 60 |
+ }) |
|
| 61 |
+ c.Assert(id, checker.Not(checker.Equals), "", check.Commentf("secrets: %s", id))
|
|
| 62 |
+ |
|
| 63 |
+ fake := d.createSecret(c, swarm.SecretSpec{
|
|
| 64 |
+ swarm.Annotations{
|
|
| 65 |
+ Name: id, |
|
| 66 |
+ }, |
|
| 67 |
+ []byte("fake foo"),
|
|
| 68 |
+ }) |
|
| 69 |
+ c.Assert(fake, checker.Not(checker.Equals), "", check.Commentf("secrets: %s", fake))
|
|
| 70 |
+ |
|
| 71 |
+ out, err := d.Cmd("secret", "ls")
|
|
| 72 |
+ c.Assert(err, checker.IsNil) |
|
| 73 |
+ c.Assert(out, checker.Contains, name) |
|
| 74 |
+ c.Assert(out, checker.Contains, fake) |
|
| 75 |
+ |
|
| 76 |
+ out, err = d.Cmd("secret", "rm", id)
|
|
| 77 |
+ c.Assert(out, checker.Contains, id) |
|
| 78 |
+ |
|
| 79 |
+ // Fake one will remain |
|
| 80 |
+ out, err = d.Cmd("secret", "ls")
|
|
| 81 |
+ c.Assert(err, checker.IsNil) |
|
| 82 |
+ c.Assert(out, checker.Not(checker.Contains), name) |
|
| 83 |
+ c.Assert(out, checker.Contains, fake) |
|
| 84 |
+ |
|
| 85 |
+ // Remove based on name prefix of the fake one |
|
| 86 |
+ // (which is the same as the ID of foo one) should not work |
|
| 87 |
+ // as search is only done based on: |
|
| 88 |
+ // - Full ID |
|
| 89 |
+ // - Full Name |
|
| 90 |
+ // - Partial ID (prefix) |
|
| 91 |
+ out, err = d.Cmd("secret", "rm", id[:5])
|
|
| 92 |
+ c.Assert(out, checker.Not(checker.Contains), id) |
|
| 93 |
+ out, err = d.Cmd("secret", "ls")
|
|
| 94 |
+ c.Assert(err, checker.IsNil) |
|
| 95 |
+ c.Assert(out, checker.Not(checker.Contains), name) |
|
| 96 |
+ c.Assert(out, checker.Contains, fake) |
|
| 97 |
+ |
|
| 98 |
+ // Remove based on ID prefix of the fake one should succeed |
|
| 99 |
+ out, err = d.Cmd("secret", "rm", fake[:5])
|
|
| 100 |
+ c.Assert(out, checker.Contains, fake) |
|
| 101 |
+ out, err = d.Cmd("secret", "ls")
|
|
| 102 |
+ c.Assert(err, checker.IsNil) |
|
| 103 |
+ c.Assert(out, checker.Not(checker.Contains), name) |
|
| 104 |
+ c.Assert(out, checker.Not(checker.Contains), id) |
|
| 105 |
+ c.Assert(out, checker.Not(checker.Contains), fake) |
|
| 106 |
+} |