blob: 6e5111af418edcc4da508d7e40e8b0f74c88f80b [file] [log] [blame]
Ryan Brownfed691e2014-09-15 13:09:40 -07001package main
2
3import (
4 "flag"
5 "fmt"
Suharsh Sivakumar23ea5352014-11-18 18:10:00 -08006 "io"
Ryan Brownfed691e2014-09-15 13:09:40 -07007 "os"
8 "os/exec"
Suharsh Sivakumar30ee6662014-10-29 18:10:07 -07009 "os/signal"
Bogdan Caprita67a62112014-12-23 17:35:55 -080010 "strconv"
Ryan Brownfed691e2014-09-15 13:09:40 -070011 "syscall"
Bogdan Caprita7f491672014-11-13 14:51:08 -080012
Cosmos Nicolaoud412cb22014-12-15 22:06:32 -080013 "golang.org/x/crypto/ssh/terminal"
Bogdan Caprita7f491672014-11-13 14:51:08 -080014
Jiri Simsaffceefa2015-02-28 11:03:34 -080015 "v.io/x/ref/lib/flags/consts"
16 vsignals "v.io/x/ref/lib/signals"
17 _ "v.io/x/ref/profiles"
18 vsecurity "v.io/x/ref/security"
19 "v.io/x/ref/security/agent"
20 "v.io/x/ref/security/agent/server"
Suharsh Sivakumaraca1c322014-10-21 11:27:32 -070021
Jiri Simsa6ac95222015-02-23 16:11:49 -080022 "v.io/v23"
23 "v.io/v23/security"
Jiri Simsa337af232015-02-27 14:36:46 -080024 "v.io/x/lib/vlog"
Ryan Brownfed691e2014-09-15 13:09:40 -070025)
26
Bogdan Capritac98a8b52014-12-01 10:08:47 -080027var (
28 keypath = flag.String("additional_principals", "", "If non-empty, allow for the creation of new principals and save them in this directory.")
29 noPassphrase = flag.Bool("no_passphrase", false, "If true, user will not be prompted for principal encryption passphrase.")
Bogdan Caprita67a62112014-12-23 17:35:55 -080030
31 // TODO(caprita): We use the exit code of the child to determine if the
32 // agent should restart it. Consider changing this to use the unix
33 // socket for this purpose.
34 restartExitCode = flag.String("restart_exit_code", "", "If non-empty, will restart the command when it exits, provided that the command's exit code matches the value of this flag. The value must be an integer, or an integer preceded by '!' (in which case all exit codes except the flag will trigger a restart.")
Bogdan Capritac98a8b52014-12-01 10:08:47 -080035)
Ryan Brown81789442014-10-30 13:23:53 -070036
Ryan Brownfed691e2014-09-15 13:09:40 -070037func main() {
Robin Thellenddd5f9e02015-02-02 14:45:46 -080038 os.Exit(Main())
39}
40
41func Main() int {
Ryan Brownfed691e2014-09-15 13:09:40 -070042 flag.Usage = func() {
43 fmt.Fprintf(os.Stderr, `Usage: %s [agent options] command command_args...
44
Bogdan Caprita7f491672014-11-13 14:51:08 -080045Loads the private key specified in privatekey.pem in %v into memory, then
Suharsh Sivakumaraca1c322014-10-21 11:27:32 -070046starts the specified command with access to the private key via the
Ryan Brownfed691e2014-09-15 13:09:40 -070047agent protocol instead of directly reading from disk.
48
Asim Shankar95910b62014-10-31 22:02:29 -070049`, os.Args[0], consts.VeyronCredentials)
Ryan Brownfed691e2014-09-15 13:09:40 -070050 flag.PrintDefaults()
51 }
Bogdan Caprita7f491672014-11-13 14:51:08 -080052 flag.Parse()
53 if len(flag.Args()) < 1 {
54 fmt.Fprintln(os.Stderr, "Need at least one argument.")
55 flag.Usage()
Robin Thellenddd5f9e02015-02-02 14:45:46 -080056 return 1
Bogdan Caprita7f491672014-11-13 14:51:08 -080057 }
Bogdan Caprita67a62112014-12-23 17:35:55 -080058 var restartOpts restartOptions
59 if err := restartOpts.parse(); err != nil {
60 fmt.Fprintln(os.Stderr, err)
61 flag.Usage()
Robin Thellenddd5f9e02015-02-02 14:45:46 -080062 return 1
Bogdan Caprita67a62112014-12-23 17:35:55 -080063 }
64
Ryan Brown10a4ade2015-02-10 13:17:18 -080065 // This is a bit tricky. We're trying to share the runtime's
66 // veyron.credentials flag. However we need to parse it before
67 // creating the runtime. We depend on the profile's init() function
68 // calling flags.CreateAndRegister(flag.CommandLine, flags.Runtime)
69 // This will read the VEYRON_CREDENTIALS env var, then our call to
70 // flag.Parse() will take any override passed on the command line.
Asim Shankar4476cc62015-02-02 15:15:33 -080071 var dir string
72 if f := flag.Lookup("veyron.credentials").Value; true {
73 dir = f.String()
Jiri Simsa6ac95222015-02-23 16:11:49 -080074 // Clear out the flag value to prevent v23.Init from
Asim Shankar4476cc62015-02-02 15:15:33 -080075 // trying to load this password protected principal.
76 f.Set("")
77 }
Suharsh Sivakumaraca1c322014-10-21 11:27:32 -070078 if len(dir) == 0 {
Cosmos Nicolaou1fcb6a32015-02-17 07:46:02 -080079 vlog.Fatalf("The %v environment variable must be set to a directory: %q", consts.VeyronCredentials, os.Getenv(consts.VeyronCredentials))
Suharsh Sivakumaraca1c322014-10-21 11:27:32 -070080 }
81
Ryan Brown81789442014-10-30 13:23:53 -070082 p, passphrase, err := newPrincipalFromDir(dir)
Suharsh Sivakumaraca1c322014-10-21 11:27:32 -070083 if err != nil {
84 vlog.Fatalf("failed to create new principal from dir(%s): %v", dir, err)
85 }
86
Jiri Simsa6ac95222015-02-23 16:11:49 -080087 // Clear out the environment variable before v23.Init.
Asim Shankar4476cc62015-02-02 15:15:33 -080088 if err = os.Setenv(consts.VeyronCredentials, ""); err != nil {
89 vlog.Fatalf("setenv: %v", err)
90 }
Jiri Simsa6ac95222015-02-23 16:11:49 -080091 ctx, shutdown := v23.Init()
Suharsh Sivakumar19fbf992015-01-23 11:02:27 -080092 defer shutdown()
Matt Rosencrantz3df85842014-12-04 16:10:45 -080093
Jiri Simsa6ac95222015-02-23 16:11:49 -080094 if ctx, err = v23.SetPrincipal(ctx, p); err != nil {
Suharsh Sivakumar19fbf992015-01-23 11:02:27 -080095 vlog.Panic("failed to set principal for ctx: %v", err)
96 }
Suharsh Sivakumar946f64d2015-01-08 10:48:13 -080097
Ryan Brown50b473a2014-09-23 14:23:00 -070098 if err = os.Setenv(agent.FdVarName, "3"); err != nil {
Matt Rosencrantz97d67a92015-01-27 21:03:12 -080099 vlog.Fatalf("setenv: %v", err)
Ryan Brownfed691e2014-09-15 13:09:40 -0700100 }
Ryan Brownfed691e2014-09-15 13:09:40 -0700101
Ryan Brown81789442014-10-30 13:23:53 -0700102 if *keypath == "" && passphrase != nil {
103 // If we're done with the passphrase, zero it out so it doesn't stay in memory
104 for i := range passphrase {
105 passphrase[i] = 0
106 }
107 passphrase = nil
108 }
109
Ryan Brownfed691e2014-09-15 13:09:40 -0700110 // Start running our server.
Ryan Brown81789442014-10-30 13:23:53 -0700111 var sock, mgrSock *os.File
Matt Rosencrantz6edab562015-01-12 11:07:55 -0800112 if sock, err = server.RunAnonymousAgent(ctx, p); err != nil {
Matt Rosencrantz97d67a92015-01-27 21:03:12 -0800113 vlog.Fatalf("RunAnonymousAgent: %v", err)
Ryan Brown81789442014-10-30 13:23:53 -0700114 }
115 if *keypath != "" {
Matt Rosencrantz6edab562015-01-12 11:07:55 -0800116 if mgrSock, err = server.RunKeyManager(ctx, *keypath, passphrase); err != nil {
Matt Rosencrantz97d67a92015-01-27 21:03:12 -0800117 vlog.Fatalf("RunKeyManager: %v", err)
Ryan Brown81789442014-10-30 13:23:53 -0700118 }
Ryan Brownfed691e2014-09-15 13:09:40 -0700119 }
120
Robin Thellenddd5f9e02015-02-02 14:45:46 -0800121 exitCode := 0
Bogdan Caprita67a62112014-12-23 17:35:55 -0800122 for {
123 // Run the client and wait for it to finish.
124 cmd := exec.Command(flag.Args()[0], flag.Args()[1:]...)
125 cmd.Stdin = os.Stdin
126 cmd.Stdout = os.Stdout
127 cmd.Stderr = os.Stderr
128 cmd.ExtraFiles = []*os.File{sock}
Ryan Brownfed691e2014-09-15 13:09:40 -0700129
Bogdan Caprita67a62112014-12-23 17:35:55 -0800130 if mgrSock != nil {
131 cmd.ExtraFiles = append(cmd.ExtraFiles, mgrSock)
Bogdan Capritab61c6752014-12-03 11:35:11 -0800132 }
Bogdan Caprita67a62112014-12-23 17:35:55 -0800133
134 err = cmd.Start()
135 if err != nil {
Matt Rosencrantz97d67a92015-01-27 21:03:12 -0800136 vlog.Fatalf("Error starting child: %v", err)
Bogdan Caprita67a62112014-12-23 17:35:55 -0800137 }
138 shutdown := make(chan struct{})
139 go func() {
140 select {
Suharsh Sivakumar946f64d2015-01-08 10:48:13 -0800141 case sig := <-vsignals.ShutdownOnSignals(ctx):
Bogdan Caprita67a62112014-12-23 17:35:55 -0800142 // TODO(caprita): Should we also relay double
143 // signal to the child? That currently just
144 // force exits the current process.
145 if sig == vsignals.STOP {
146 sig = syscall.SIGTERM
147 }
148 cmd.Process.Signal(sig)
149 case <-shutdown:
150 }
151 }()
152 cmd.Wait()
153 close(shutdown)
154 exitCode = cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus()
155 if !restartOpts.restart(exitCode) {
156 break
157 }
158 }
159 // TODO(caprita): If restartOpts.enabled is false, we could close these
160 // right after cmd.Start().
161 sock.Close()
162 mgrSock.Close()
Robin Thellenddd5f9e02015-02-02 14:45:46 -0800163 return exitCode
Ryan Brownfed691e2014-09-15 13:09:40 -0700164}
Suharsh Sivakumaraca1c322014-10-21 11:27:32 -0700165
Ryan Brown81789442014-10-30 13:23:53 -0700166func newPrincipalFromDir(dir string) (security.Principal, []byte, error) {
Suharsh Sivakumaraca1c322014-10-21 11:27:32 -0700167 p, err := vsecurity.LoadPersistentPrincipal(dir, nil)
168 if os.IsNotExist(err) {
169 return handleDoesNotExist(dir)
170 }
Suharsh Sivakumar4684f4e2014-10-24 13:42:06 -0700171 if err == vsecurity.PassphraseErr {
172 return handlePassphrase(dir)
Suharsh Sivakumaraca1c322014-10-21 11:27:32 -0700173 }
Ryan Brown81789442014-10-30 13:23:53 -0700174 return p, nil, err
Suharsh Sivakumaraca1c322014-10-21 11:27:32 -0700175}
176
Ryan Brown81789442014-10-30 13:23:53 -0700177func handleDoesNotExist(dir string) (security.Principal, []byte, error) {
Bogdan Capritac98a8b52014-12-01 10:08:47 -0800178 fmt.Println("Private key file does not exist. Creating new private key...")
179 var pass []byte
180 if !*noPassphrase {
181 var err error
182 if pass, err = getPassword("Enter passphrase (entering nothing will store unencrypted): "); err != nil {
183 return nil, nil, fmt.Errorf("failed to read passphrase: %v", err)
184 }
Suharsh Sivakumaraca1c322014-10-21 11:27:32 -0700185 }
Suharsh Sivakumar30ee6662014-10-29 18:10:07 -0700186 p, err := vsecurity.CreatePersistentPrincipal(dir, pass)
Suharsh Sivakumar8a7fba42014-10-27 12:40:48 -0700187 if err != nil {
Ryan Brown81789442014-10-30 13:23:53 -0700188 return nil, pass, err
Suharsh Sivakumar8a7fba42014-10-27 12:40:48 -0700189 }
190 vsecurity.InitDefaultBlessings(p, "agent_principal")
Ryan Brown81789442014-10-30 13:23:53 -0700191 return p, pass, nil
Suharsh Sivakumaraca1c322014-10-21 11:27:32 -0700192}
193
Ryan Brown81789442014-10-30 13:23:53 -0700194func handlePassphrase(dir string) (security.Principal, []byte, error) {
Bogdan Capritac98a8b52014-12-01 10:08:47 -0800195 if *noPassphrase {
196 return nil, nil, fmt.Errorf("Passphrase required for decrypting principal.")
197 }
Suharsh Sivakumar23ea5352014-11-18 18:10:00 -0800198 pass, err := getPassword("Private key file is encrypted. Please enter passphrase.\nEnter passphrase: ")
Suharsh Sivakumaraca1c322014-10-21 11:27:32 -0700199 if err != nil {
Ryan Brown81789442014-10-30 13:23:53 -0700200 return nil, nil, fmt.Errorf("failed to read passphrase: %v", err)
Suharsh Sivakumaraca1c322014-10-21 11:27:32 -0700201 }
Ryan Brown81789442014-10-30 13:23:53 -0700202 p, err := vsecurity.LoadPersistentPrincipal(dir, pass)
203 return p, pass, err
Suharsh Sivakumar30ee6662014-10-29 18:10:07 -0700204}
205
206func getPassword(prompt string) ([]byte, error) {
Jiri Simsaab5f4ff2014-11-18 15:31:48 -0800207 if !terminal.IsTerminal(int(os.Stdin.Fd())) {
Suharsh Sivakumar23ea5352014-11-18 18:10:00 -0800208 // If the standard input is not a terminal, the password is obtained by reading a line from it.
209 return readPassword()
Jiri Simsaab5f4ff2014-11-18 15:31:48 -0800210 }
Suharsh Sivakumar30ee6662014-10-29 18:10:07 -0700211 fmt.Printf(prompt)
212 stop := make(chan bool)
213 defer close(stop)
214 state, err := terminal.GetState(int(os.Stdin.Fd()))
215 if err != nil {
216 return nil, err
217 }
218 go catchTerminationSignals(stop, state)
Suharsh Sivakumar23ea5352014-11-18 18:10:00 -0800219 defer fmt.Printf("\n")
Suharsh Sivakumar30ee6662014-10-29 18:10:07 -0700220 return terminal.ReadPassword(int(os.Stdin.Fd()))
221}
222
Suharsh Sivakumar23ea5352014-11-18 18:10:00 -0800223// readPassword reads form Stdin until it sees '\n' or EOF.
224func readPassword() ([]byte, error) {
225 var pass []byte
226 var total int
227 for {
228 b := make([]byte, 1)
229 count, err := os.Stdin.Read(b)
230 if err != nil && err != io.EOF {
231 return nil, err
232 }
233 if err == io.EOF || b[0] == '\n' {
234 return pass[:total], nil
235 }
236 total += count
237 pass = secureAppend(pass, b)
238 }
239}
240
241func secureAppend(s, t []byte) []byte {
242 res := append(s, t...)
243 if len(res) > cap(s) {
244 // When append needs to allocate a new array, clear out the old one.
245 for i := range s {
246 s[i] = '0'
247 }
248 }
249 // Clear out the second array.
250 for i := range t {
251 t[i] = '0'
252 }
253 return res
254}
255
Suharsh Sivakumar30ee6662014-10-29 18:10:07 -0700256// catchTerminationSignals catches signals to allow us to turn terminal echo back on.
257func catchTerminationSignals(stop <-chan bool, state *terminal.State) {
258 var successErrno syscall.Errno
259 sig := make(chan os.Signal, 4)
260 // Catch the blockable termination signals.
261 signal.Notify(sig, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGHUP)
262 select {
263 case <-sig:
264 // Start on new line in terminal.
265 fmt.Printf("\n")
266 if err := terminal.Restore(int(os.Stdin.Fd()), state); err != successErrno {
267 vlog.Errorf("Failed to restore terminal state (%v), you words may not show up when you type, enter 'stty echo' to fix this.", err)
268 }
269 os.Exit(-1)
270 case <-stop:
271 signal.Stop(sig)
272 }
Suharsh Sivakumaraca1c322014-10-21 11:27:32 -0700273}
Bogdan Caprita67a62112014-12-23 17:35:55 -0800274
275type restartOptions struct {
276 enabled, unless bool
277 code int
278}
279
280func (opts *restartOptions) parse() error {
281 code := *restartExitCode
282 if code == "" {
283 return nil
284 }
285 opts.enabled = true
286 if code[0] == '!' {
287 opts.unless = true
288 code = code[1:]
289 }
290 var err error
291 if opts.code, err = strconv.Atoi(code); err != nil {
292 return fmt.Errorf("Failed to parse restart exit code: %v", err)
293 }
294 return nil
295}
296
297func (opts *restartOptions) restart(exitCode int) bool {
298 return opts.enabled && opts.unless != (exitCode == opts.code)
299}