8000 Add support for using Configs as CredentialSpecs in services · moby/moby@20383d5 · GitHub
[go: up one dir, main page]

Skip to content

Commit 20383d5

Browse files
thaJeztahdperny
authored andcommitted
Add support for using Configs as CredentialSpecs in services
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
1 parent 04995fa commit 20383d5

File tree

7 files changed

+300
-25
lines changed

7 files changed

+300
-25
lines changed

api/server/router/swarm/cluster_routes.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,11 @@ func (sr *swarmRouter) createService(ctx context.Context, w http.ResponseWriter,
218218
// Sysctls for docker swarm services weren't supported before
219219
// API version 1.40
220220
service.TaskTemplate.ContainerSpec.Sysctls = nil
221+
222+
if service.TaskTemplate.ContainerSpec.Privileges != nil && service.TaskTemplate.ContainerSpec.Privileges.CredentialSpec != nil {
223+
// Support for setting credential-spec through configs was added in API 1.40
224+
service.TaskTemplate.ContainerSpec.Privileges.CredentialSpec.Config = ""
225+
}
221226
}
222227

223228
if service.TaskTemplate.Placement != nil {
@@ -270,6 +275,11 @@ func (sr *swarmRouter) updateService(ctx context.Context, w http.ResponseWriter,
270275
// Sysctls for docker swarm services weren't supported before
271276
// API version 1.40
272277
service.TaskTemplate.ContainerSpec.Sysctls = nil
278+
279+
if service.TaskTemplate.ContainerSpec.Privileges != nil && service.TaskTemplate.ContainerSpec.Privileges.CredentialSpec != nil {
280+
// Support for setting credential-spec through configs was added in API 1.40
281+
service.TaskTemplate.ContainerSpec.Privileges.CredentialSpec.Config = ""
282+
}
273283
}
274284

275285
if service.TaskTemplate.Placement != nil {

api/swagger.yaml

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2623,8 +2623,19 @@ definitions:
26232623
type: "object"
26242624
description: "CredentialSpec for managed service account (Windows only)"
26252625
properties:
2626+
Config:
2627+
type: "string"
2628+
example: "0bt9dmxjvjiqermk6xrop3ekq"
2629+
description: |
2630+
Load credential spec from a Swarm Config with the given ID.
2631+
2632+
<p><br /></p>
2633+
2634+
2635+
> **Note**: `CredentialSpec.File`, `CredentialSpec.Registry`, and `CredentialSpec.Config` are mutually exclusive.
26262636
File:
26272637
type: "string"
2638+
example: "spec.json"
26282639
description: |
26292640
Load credential spec from this file. The file is read by the daemon, and must be present in the
26302641
`CredentialSpecs` subdirectory in the docker data directory, which defaults to
@@ -2634,7 +2645,7 @@ definitions:
26342645
26352646
<p><br /></p>
26362647
2637-
> **Note**: `CredentialSpec.File` and `CredentialSpec.Registry` are mutually exclusive.
2648+
> **Note**: `CredentialSpec.File`, `CredentialSpec.Registry`, and `CredentialSpec.Config` are mutually exclusive.
26382649
Registry:
26392650
type: "string"
26402651
description: |
@@ -2646,7 +2657,7 @@ definitions:
26462657
<p><br /></p>
26472658
26482659
2649-
> **Note**: `CredentialSpec.File` and `CredentialSpec.Registry` are mutually exclusive.
2660+
> **Note**: `CredentialSpec.File`, `CredentialSpec.Registry`, and `CredentialSpec.Config` are mutually exclusive.
26502661
SELinuxContext:
26512662
type: "object"
26522663
description: "SELinux labels of the container"

api/types/swarm/container.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ type SELinuxContext struct {
3333

3434
// CredentialSpec for managed service account (Windows only)
3535
type CredentialSpec struct {
36+
Config string
3637
File string
3738
Registry string
3839
}

daemon/cluster/convert/container.go

Lines changed: 60 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package convert // import "github.com/docker/docker/daemon/cluster/convert"
22

33
import (
4-
"errors"
54
"fmt"
65
"strings"
76

@@ -10,6 +9,7 @@ import (
109
types "github.com/docker/docker/api/types/swarm"
1110
swarmapi "github.com/docker/swarmkit/api"
1211
gogotypes "github.com/gogo/protobuf/types"
12+
"github.com/pkg/errors"
1313
"github.com/sirupsen/logrus"
1414
)
1515

@@ -52,13 +52,7 @@ func containerSpecFromGRPC(c *swarmapi.ContainerSpec) *types.ContainerSpec {
5252
containerSpec.Privileges = &types.Privileges{}
5353

5454
if c.Privileges.CredentialSpec != nil {
55-
containerSpec.Privileges.CredentialSpec = &types.CredentialSpec{}
56-
switch c.Privileges.CredentialSpec.Source.(type) {
57-
case *swarmapi.Privileges_CredentialSpec_File:
58-
containerSpec.Privileges.CredentialSpec.File = c.Privileges.CredentialSpec.GetFile()
59-
case *swarmapi.Privileges_CredentialSpec_Registry:
60-
containerSpec.Privileges.CredentialSpec.Registry = c.Privileges.CredentialSpec.GetRegistry()
61-
}
55+
containerSpec.Privileges.CredentialSpec = credentialSpecFromGRPC(c.Privileges.CredentialSpec)
6256
}
6357

6458
if c.Privileges.SELinuxContext != nil {
@@ -272,22 +266,11 @@ func containerToGRPC(c *types.ContainerSpec) (*swarmapi.ContainerSpec, error) {
272266
containerSpec.Privileges = &swarmapi.Privileges{}
273267

274268
if c.Privileges.CredentialSpec != nil {
275-
containerSpec.Privileges.CredentialSpec = &swarmapi.Privileges_CredentialSpec{}
276-
277-
if c.Privileges.CredentialSpec.File != "" && c.Privileges.CredentialSpec.Registry != "" {
278-
return nil, errors.New("cannot specify both \"file\" and \"registry\" credential specs")
279-
}
280-
if c.Privileges.CredentialSpec.File != "" {
281-
containerSpec.Privileges.CredentialSpec.Source = &swarmapi.Privileges_CredentialSpec_File{
282-
File: c.Privileges.CredentialSpec.File,
283-
}
284-
} else if c.Privileges.CredentialSpec.Registry != "" {
285-
containerSpec.Privileges.CredentialSpec.Source = &swarmapi.Privileges_CredentialSpec_Registry{
286-
Registry: c.Privileges.CredentialSpec.Registry,
287-
}
288-
} else {
289-
return nil, errors.New("must either provide \"file\" or \"registry\" for credential spec")
269+
cs, err := credentialSpecToGRPC(c.Privileges.CredentialSpec)
270+
if err != nil {
271+
return nil, errors.Wrap(err, "invalid CredentialSpec")
290272
}
273+
containerSpec.Privileges.CredentialSpec = cs
291274
}
292275

293276
if c.Privileges.SELinuxContext != nil {
@@ -359,6 +342,60 @@ func containerToGRPC(c *types.ContainerSpec) (*swarmapi.ContainerSpec, error) {
359342
return containerSpec, nil
360343
}
361344

345+
func credentialSpecFromGRPC(c *swarmapi.Privileges_CredentialSpec) *types.CredentialSpec {
346+
cs := &types.CredentialSpec{}
347+
switch c.Source.(type) {
348+
case *swarmapi.Privileges_CredentialSpec_Config:
349+
cs.Config = c.GetConfig()
350+
case *swarmapi.Privileges_CredentialSpec_File:
351+
cs.File = c.GetFile()
352+
case *swarmapi.Privileges_CredentialSpec_Registry:
353+
cs.Registry = c.GetRegistry()
354+
}
355+
return cs
356+
}
357+
358+
func credentialSpecToGRPC(c *types.CredentialSpec) (*swarmapi.Privileges_CredentialSpec, error) {
359+
var opts []string
360+
361+
if c.Config != "" {
362+
opts = append(opts, `"config"`)
363+
}
364+
if c.File != "" {
365+
opts = append(opts, `"file"`)
366+
}
367+
if c.Registry != "" {
368+
opts = append(opts, `"registry"`)
369+
}
370+
l := len(opts)
371+
switch {
372+
case l == 0:
373+
return nil, errors.New(`must either provide "file", "registry", or "config" for credential spec`)
374+
case l == 2:
375+
return nil, fmt.Errorf("cannot specify both %s and %s credential specs", opts[0], opts[1])
376+
case l > 2:
377+
return nil, fmt.Errorf("cannot specify both %s, and %s credential specs", strings.Join(opts[:l-1], ", "), opts[l-1])
378+
}
379+
380+
spec := &swarmapi.Privileges_CredentialSpec{}
381+
switch {
382+
case c.Config != "":
383+
spec.Source = &swarmapi.Privileges_CredentialSpec_Config{
384+
Config: c.Config,
385+
}
386+
case c.File != "":
387+
spec.Source = &swarmapi.Privileges_CredentialSpec_File{
388+
File: c.File,
389+
}
390+
case c.Registry != "":
391+
spec.Source = &swarmapi.Privileges_CredentialSpec_Registry{
392+
Registry: c.Registry,
393+
}
394+
}
395+
396+
return spec, nil
397+
}
398+
362399
func healthConfigFromGRPC(h *swarmapi.HealthConfig) *container.HealthConfig {
363400
interval, _ := gogotypes.DurationFromProto(h.Interval)
364401
timeout, _ := gogotypes.DurationFromProto(h.Timeout)

daemon/cluster/convert/service_test.go

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,167 @@ func TestServiceConvertFromGRPCIsolation(t *testing.T) {
233233
}
234234
}
235235

236+
func TestServiceConvertToGRPCCredentialSpec(t *testing.T) {
237+
cases := []struct {
238+
name string
239+
from swarmtypes.CredentialSpec
240+
to swarmapi.Privileges_CredentialSpec
241+
expectedErr string
242+
}{
243+
{
244+
name: "empty credential spec",
245+
from: swarmtypes.CredentialSpec{},
246+
to: swarmapi.Privileges_CredentialSpec{},
247+
expectedErr: `invalid CredentialSpec: must either provide "file", "registry", or "config" for credential spec`,
248+
},
249+
{
250+
name: "config and file credential spec",
251+
from: swarmtypes.CredentialSpec{
252+
Config: "0bt9dmxjvjiqermk6xrop3ekq",
253+
File: "spec.json",
254+
},
255+
to: swarmapi.Privileges_CredentialSpec{},
256+
expectedErr: `invalid CredentialSpec: cannot specify both "config" and "file" credential specs`,
257+
},
258+
{
259+
name: "config and registry credential spec",
260+
from: swarmtypes.CredentialSpec{
261+
Config: "0bt9dmxjvjiqermk6xrop3ekq",
262+
Registry: "testing",
263+
},
264+
to: swarmapi.Privileges_CredentialSpec{},
265+
expectedErr: `invalid CredentialSpec: cannot specify both "config" and "registry" credential specs`,
266+
},
267+
{
268+
name: "file and registry credential spec",
269+
from: swarmtypes.CredentialSpec{
270+
File: "spec.json",
271+
Registry: "testing",
272+
},
273+
to: swarmapi.Privileges_CredentialSpec{},
274+
expectedErr: `invalid CredentialSpec: cannot specify both "file" and "registry" credential specs`,
275+
},
276+
{
277+
name: "config and file and registry credential spec",
278+
from: swarmtypes.CredentialSpec{
279+
Config: "0bt9dmxjvjiqermk6xrop3ekq",
280+
File: "spec.json",
281+
Registry: "testing",
282+
},
283+
to: swarmapi.Privileges_CredentialSpec{},
284+
expectedErr: `invalid CredentialSpec: cannot specify both "config", "file", and "registry" credential specs`,
285+
},
286+
{
287+
name: "config credential spec",
288+
from: swarmtypes.CredentialSpec{Config: "0bt9dmxjvjiqermk6xrop3ekq"},
289+
to: swarmapi.Privileges_CredentialSpec{
290+
Source: &swarmapi.Privileges_CredentialSpec_Config{Config: "0bt9dmxjvjiqermk6xrop3ekq"},
291+
},
292+
},
293+
{
294+
name: "file credential spec",
295+
from: swarmtypes.CredentialSpec{File: "foo.json"},
296+
to: swarmapi.Privileges_CredentialSpec{
297+
Source: &swarmapi.Privileges_CredentialSpec_File{File: "foo.json"},
298+
},
299+
},
300+
{
301+
name: "registry credential spec",
302+
from: swarmtypes.CredentialSpec{Registry: "testing"},
303+
to: swarmapi.Privileges_CredentialSpec{
304+
Source: &swarmapi.Privileges_CredentialSpec_Registry{Registry: "testing"},
305+
},
306+
},
307+
}
308+
309+
for _, c := range cases {
310+
c := c
311+
t.Run(c.name, func(t *testing.T) {
312+
s := swarmtypes.ServiceSpec{
313+
TaskTemplate: swarmtypes.TaskSpec{
314+
ContainerSpec: &swarmtypes.ContainerSpec{
315+
Privileges: &swarmtypes.Privileges{
316+
CredentialSpec: &c.from,
317+
},
318+
},
319+
},
320+
}
321+
322+
res, err := ServiceSpecToGRPC(s)
323+
if c.expectedErr != "" {
324+
assert.Error(t, err, c.expectedErr)
325+
return
326+
}
327+
328+
assert.NilError(t, err)
329+
v, ok := res.Task.Runtime.(*swarmapi.TaskSpec_Container)
330+
if !ok {
331+
t.Fatal("expected type swarmapi.TaskSpec_Container")
332+
}
333+
assert.DeepEqual(t, c.to, *v.Container.Privileges.CredentialSpec)
334+
})
335+
}
336+
}
337+
338+
func TestServiceConvertFromGRPCCredentialSpec(t *testing.T) {
339+
cases := []struct {
340+
name string
341+
from swarmapi.Privileges_CredentialSpec
342+
to *swarmtypes.CredentialSpec
343+
}{
344+
{
345+
name: "empty credential spec",
346+
from: swarmapi.Privileges_CredentialSpec{},
347+
to: &swarmtypes.CredentialSpec{},
348+
},
349+
{
350+
name: "config credential spec",
351+
from: swarmapi.Privileges_CredentialSpec{
352+
Source: &swarmapi.Privileges_CredentialSpec_Config{Config: "0bt9dmxjvjiqermk6xrop3ekq"},
353+
},
354+
to: &swarmtypes.CredentialSpec{Config: "0bt9dmxjvjiqermk6xrop3ekq"},
355+
},
356+
{
357+
name: "file credential spec",
358+
from: swarmapi.Privileges_CredentialSpec{
359+
Source: &swarmapi.Privileges_CredentialSpec_File{File: "foo.json"},
360+
},
361+
to: &swarmtypes.CredentialSpec{File: "foo.json"},
362+
},
363+
{
364+
name: "registry credential spec",
365+
from: swarmapi.Privileges_CredentialSpec{
366+
Source: &swarmapi.Privileges_CredentialSpec_Registry{Registry: "testing"},
367+
},
368+
to: &swarmtypes.CredentialSpec{Registry: "testing"},
369+
},
370+
}
371+
372+
for _, tc := range cases {
373+
tc := tc
374+
375+
t.Run(tc.name, func(t *testing.T) {
376+
gs := swarmapi.Service{
377+
Spec: swarmapi.ServiceSpec{
378+
Task: swarmapi.TaskSpec{
379+
Runtime: &swarmapi.TaskSpec_Container{
380+
Container: &swarmapi.ContainerSpec{
381+
Privileges: &swarmapi.Privileges{
382+
CredentialSpec: &tc.from,
383+
},
384+
},
385+
},
386+
},
387+
},
388+
}
389+
390+
svc, err := ServiceFromGRPC(gs)
391+
assert.NilError(t, err)
392+
assert.DeepEqual(t, svc.Spec.TaskTemplate.ContainerSpec.Privileges.CredentialSpec, tc.to)
393+
})
394+
}
395+
}
396+
236397
func TestServiceConvertToGRPCNetworkAtachmentRuntime(t *testing.T) {
237398
someid := "asfjkl"
238399
s := swarmtypes.ServiceSpec{

0 commit comments

Comments
 (0)
0