@@ -52,14 +52,19 @@ func newSecretCreateCommand(dockerCli command.Cli) *cobra.Command {
5252func runSecretCreate (ctx context.Context , dockerCli command.Cli , options createOptions ) error {
5353 client := dockerCli .Client ()
5454
55- if options .driver != "" && options .file != "" {
56- return errors .Errorf ("When using secret driver secret data must be empty" )
55+ var secretData []byte
56+ if options .driver != "" {
57+ if options .file != "" {
58+ return errors .Errorf ("When using secret driver secret data must be empty" )
59+ }
60+ } else {
61+ var err error
62+ secretData , err = readSecretData (dockerCli .In (), options .file )
63+ if err != nil {
64+ return err
65+ }
5766 }
5867
59- secretData , err := readSecretData (dockerCli .In (), options .file )
60- if err != nil {
61- return errors .Errorf ("Error reading content from %q: %v" , options .file , err )
62- }
6368 spec := swarm.SecretSpec {
6469 Annotations : swarm.Annotations {
6570 Name : options .name ,
@@ -82,26 +87,54 @@ func runSecretCreate(ctx context.Context, dockerCli command.Cli, options createO
8287 return err
8388 }
8489
85- fmt .Fprintln (dockerCli .Out (), r .ID )
90+ _ , _ = fmt .Fprintln (dockerCli .Out (), r .ID )
8691 return nil
8792}
8893
89- func readSecretData (in io.ReadCloser , file string ) ([]byte , error ) {
90- // Read secret value from external driver
91- if file == "" {
92- return nil , nil
93- }
94- if file != "-" {
95- var err error
96- in , err = sequential .Open (file )
94+ // maxSecretSize is the maximum byte length of the [swarm.SecretSpec.Data] field,
95+ // as defined by [MaxSecretSize] in SwarmKit.
96+ //
97+ // [MaxSecretSize]: https://pkg.go.dev/github.com/moby/swarmkit/[email protected] /api/validation#MaxSecretSize 98+ const maxSecretSize = 500 * 1024 // 500KB
99+
100+ // readSecretData reads the secret from either stdin or the given fileName.
101+ //
102+ // It reads up to twice the maximum size of the secret ([maxSecretSize]),
103+ // just in case swarm's limit changes; this is only a safeguard to prevent
104+ // reading arbitrary files into memory.
105+ func readSecretData (in io.Reader , fileName string ) ([]byte , error ) {
106+ switch fileName {
107+ case "-" :
108+ data , err := io .ReadAll (io .LimitReader (in , 2 * maxSecretSize ))
97109 if err != nil {
98- return nil , err
110+ return nil , fmt . Errorf ( "error reading from STDIN: %w" , err )
99111 }
100- defer in .Close ()
101- }
102- data , err := io .ReadAll (in )
103- if err != nil {
104- return nil , err
112+ if len (data ) == 0 {
113+ return nil , errors .New ("error reading from STDIN: data is empty" )
114+ }
115+ return data , nil
116+ case "" :
117+ return nil , errors .New ("secret file is required" )
118+ default :
119+ // Open file with [FILE_FLAG_SEQUENTIAL_SCAN] on Windows, which
120+ // prevents Windows from aggressively caching it. We expect this
121+ // file to be only read once. Given that this is expected to be
122+ // a small file, this may not be a significant optimization, so
123+ // we could choose to omit this, and use a regular [os.Open].
124+ //
125+ // [FILE_FLAG_SEQUENTIAL_SCAN]: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea#FILE_FLAG_SEQUENTIAL_SCAN
126+ f , err := sequential .Open (fileName )
127+ if err != nil {
128+ return nil , fmt .Errorf ("error reading from %s: %w" , fileName , err )
129+ }
130+ defer f .Close ()
131+ data , err := io .ReadAll (io .LimitReader (f , 2 * maxSecretSize ))
132+ if err != nil {
133+ return nil , fmt .Errorf ("error reading from %s: %w" , fileName , err )
134+ }
135+ if len (data ) == 0 {
136+ return nil , fmt .Errorf ("error reading from %s: data is empty" , fileName )
137+ }
138+ return data , nil
105139 }
106- return data , nil
107140}
0 commit comments