@@ -4,9 +4,11 @@ import (
44 "crypto/rand"
55 "encoding/hex"
66 "fmt"
7+ "io"
78 "os"
89 "path/filepath"
910 "strings"
11+ "syscall"
1012)
1113
1214// BootstrapTokenFileEnv names the file the bootstrap token is read from when
@@ -54,10 +56,8 @@ func EnsureBootstrapToken() (created bool, path string, err error) {
5456}
5557
5658func loadOrCreateBootstrapToken (path string ) (string , bool , error ) {
57- if content , err := os .ReadFile (path ); err == nil {
58- if token := strings .TrimSpace (string (content )); token != "" {
59- return token , false , nil
60- }
59+ if token , err := readBootstrapToken (path ); err == nil && token != "" {
60+ return token , false , nil
6161 }
6262
6363 raw := make ([]byte , bootstrapTokenBytes )
@@ -75,15 +75,16 @@ func loadOrCreateBootstrapToken(path string) (string, bool, error) {
7575 if ! os .IsExist (err ) {
7676 return "" , false , err
7777 }
78- content , readErr := os . ReadFile (path )
78+ token , readErr := readBootstrapToken (path )
7979 if readErr != nil {
8080 return "" , false , readErr
8181 }
82- if token := strings . TrimSpace ( string ( content )); token != "" {
82+ if token != "" {
8383 return token , false , nil
8484 }
85- // Empty leftover file: replace it. Do not truncate a non-empty secret.
86- if err := os .WriteFile (path , []byte (value + "\n " ), bootstrapTokenFileMode ); err != nil {
85+ // Empty leftover file: replace it without following a symlink. Do not
86+ // truncate a non-empty secret.
87+ if err := writeBootstrapTokenNoFollow (path , value ); err != nil {
8788 return "" , false , err
8889 }
8990 return value , true , nil
@@ -97,3 +98,36 @@ func loadOrCreateBootstrapToken(path string) (string, bool, error) {
9798 }
9899 return value , true , nil
99100}
101+
102+ // readBootstrapToken reads the token without following a symlink. A link placed
103+ // on the shared volume must not be treated as the token file.
104+ func readBootstrapToken (path string ) (string , error ) {
105+ file , err := os .OpenFile (path , os .O_RDONLY | syscall .O_NOFOLLOW , 0 )
106+ if err != nil {
107+ return "" , err
108+ }
109+ defer func () {
110+ _ = file .Close ()
111+ }()
112+ // Read the whole file. A prefix must not be treated as the token: the console
113+ // sends the file contents, and a truncated value would never match.
114+ content , err := io .ReadAll (file )
115+ if err != nil {
116+ return "" , err
117+ }
118+ return strings .TrimSpace (string (content )), nil
119+ }
120+
121+ // writeBootstrapTokenNoFollow overwrites an existing regular file. O_NOFOLLOW
122+ // refuses a symlink instead of writing through it.
123+ func writeBootstrapTokenNoFollow (path , value string ) error {
124+ file , err := os .OpenFile (path , os .O_WRONLY | os .O_TRUNC | syscall .O_NOFOLLOW , bootstrapTokenFileMode )
125+ if err != nil {
126+ return err
127+ }
128+ if _ , err = file .WriteString (value + "\n " ); err != nil {
129+ _ = file .Close ()
130+ return err
131+ }
132+ return file .Close ()
133+ }
0 commit comments