de9cfb3c154a63b01df78aee1522f57b20c90260
[arvados.git] / services / boot / vault.go
1 package main
2
3 import (
4         "context"
5         "fmt"
6         "io/ioutil"
7         "log"
8         "path"
9         "sync"
10
11         "github.com/hashicorp/vault/api"
12 )
13
14 var (
15         vault    = &vaultBooter{}
16         vaultCfg = api.DefaultConfig()
17 )
18
19 type vaultBooter struct {
20         sync.Mutex
21 }
22
23 func (vb *vaultBooter) Boot(ctx context.Context) error {
24         vb.Lock()
25         defer vb.Unlock()
26
27         if vb.check(ctx) == nil {
28                 return nil
29         }
30         cfg := cfg(ctx)
31         bin := cfg.UsrDir + "/bin/vault"
32         err := (&download{
33                 URL:  "https://releases.hashicorp.com/vault/0.6.4/vault_0.6.4_linux_amd64.zip",
34                 Dest: bin,
35                 Size: 52518022,
36                 Mode: 0755,
37         }).Boot(ctx)
38         if err != nil {
39                 return err
40         }
41
42         cfgPath := path.Join(cfg.DataDir, "vault.hcl")
43         err = atomicWriteFile(cfgPath, []byte(fmt.Sprintf(`backend "consul" {
44                 address = "127.0.0.1:%d"
45                 path = "vault"
46         }
47         listener "tcp" {
48                 address = "127.0.0.1:%d"
49                 tls_disable = 1
50         }`, cfg.Ports.ConsulHTTP, cfg.Ports.VaultServer)), 0644)
51         if err != nil {
52                 return err
53         }
54
55         args := []string{"server", "-config=" + cfgPath}
56         supervisor := newSupervisor(ctx, "arvados-vault", bin, args...)
57         running, err := supervisor.Running(ctx)
58         if err != nil {
59                 return err
60         }
61         if !running {
62                 defer feedbackf(ctx, "starting vault service")()
63                 err = supervisor.Start(ctx)
64                 if err != nil {
65                         return fmt.Errorf("starting vault: %s", err)
66                 }
67         }
68
69         vb.tryInit(ctx)
70         return vb.check(ctx)
71 }
72
73 func (vb *vaultBooter) tryInit(ctx context.Context) {
74         cfg := cfg(ctx)
75         vault, err := vb.client(ctx)
76         if err != nil {
77                 return
78         }
79         if init, err := vault.Sys().InitStatus(); err != nil {
80                 log.Printf("error: vault InitStatus: %s", err)
81                 return
82         } else if init {
83                 return
84         }
85         resp, err := vault.Sys().Init(&api.InitRequest{
86                 SecretShares:    5,
87                 SecretThreshold: 3,
88         })
89         if err != nil {
90                 log.Printf("vault-init: %s", err)
91                 return
92         }
93         atomicWriteJSON(path.Join(cfg.DataDir, "vault-keys.json"), resp, 0400)
94         atomicWriteFile(path.Join(cfg.DataDir, "vault-root-token.txt"), []byte(resp.RootToken), 0400)
95
96         for _, key := range resp.Keys {
97                 resp, err := vault.Sys().Unseal(key)
98                 if err != nil {
99                         log.Printf("error: unseal: %s", err)
100                         continue
101                 }
102                 if !resp.Sealed {
103                         log.Printf("unseal successful")
104                         break
105                 }
106         }
107 }
108
109 func (vb *vaultBooter) client(ctx context.Context) (*api.Client, error) {
110         cfg := cfg(ctx)
111         vaultCfg.Address = fmt.Sprintf("http://0.0.0.0:%d", cfg.Ports.VaultServer)
112         return api.NewClient(vaultCfg)
113 }
114
115 func (vb *vaultBooter) check(ctx context.Context) error {
116         cfg := cfg(ctx)
117         vault, err := vb.client(ctx)
118         if err != nil {
119                 return err
120         }
121         token, err := ioutil.ReadFile(path.Join(cfg.DataDir, "vault-root-token.txt"))
122         if err != nil {
123                 return err
124         }
125         vault.SetToken(string(token))
126         if init, err := vault.Sys().InitStatus(); err != nil {
127                 return err
128         } else if !init {
129                 return fmt.Errorf("vault is not initialized")
130         }
131         return nil
132 }