f01dbb626fa80015e96cd831cba253b68efb8a93
[arvados.git] / services / boot / vault.go
1 package main
2
3 import (
4         "context"
5         "encoding/base64"
6         "fmt"
7         "io/ioutil"
8         "log"
9         "path"
10         "sync"
11         "time"
12
13         consulAPI "github.com/hashicorp/consul/api"
14         "github.com/hashicorp/vault/api"
15 )
16
17 var (
18         vault    = &vaultBooter{}
19         vaultCfg = api.DefaultConfig()
20 )
21
22 type vaultBooter struct {
23         sync.Mutex
24 }
25
26 func (vb *vaultBooter) Boot(ctx context.Context) error {
27         vb.Lock()
28         defer vb.Unlock()
29
30         if vb.check(ctx) == nil {
31                 return nil
32         }
33         cfg := cfg(ctx)
34         bin := cfg.UsrDir + "/bin/vault"
35         err := (&download{
36                 URL:  "https://releases.hashicorp.com/vault/0.6.4/vault_0.6.4_linux_amd64.zip",
37                 Dest: bin,
38                 Size: 52518022,
39                 Mode: 0755,
40         }).Boot(ctx)
41         if err != nil {
42                 return err
43         }
44
45         masterToken, err := ioutil.ReadFile(cfg.masterTokenFile())
46         if err != nil {
47                 return err
48         }
49
50         cfgPath := path.Join(cfg.DataDir, "vault.hcl")
51         err = atomicWriteFile(cfgPath, []byte(fmt.Sprintf(`backend "consul" {
52                 address = "127.0.0.1:%d"
53                 path = "vault"
54                 token = %q
55         }
56         listener "tcp" {
57                 address = "127.0.0.1:%d"
58                 tls_disable = 1
59         }`, cfg.Ports.ConsulHTTP, masterToken, cfg.Ports.VaultServer)), 0644)
60         if err != nil {
61                 return err
62         }
63
64         args := []string{"server", "-config=" + cfgPath}
65         supervisor := newSupervisor(ctx, "arvados-vault", bin, args...)
66         running, err := supervisor.Running(ctx)
67         if err != nil {
68                 return err
69         }
70         if !running {
71                 defer feedbackf(ctx, "starting vault service")()
72                 err = supervisor.Start(ctx)
73                 if err != nil {
74                         return fmt.Errorf("starting vault: %s", err)
75                 }
76         }
77
78         if err := vb.tryInit(ctx); err != nil {
79                 return err
80         }
81         return waitCheck(ctx, 30*time.Second, vb.check)
82 }
83
84 func (vb *vaultBooter) tryInit(ctx context.Context) error {
85         cfg := cfg(ctx)
86
87         var vault *api.Client
88         var init bool
89         if err := waitCheck(ctx, time.Minute, func(context.Context) error {
90                 var err error
91                 vault, err = vb.client(ctx)
92                 if err != nil {
93                         return err
94                 }
95                 init, err = vault.Sys().InitStatus()
96                 return err
97         }); err != nil {
98                 return err
99         } else if init {
100                 return nil
101         }
102
103         resp, err := vault.Sys().Init(&api.InitRequest{
104                 SecretShares:    5,
105                 SecretThreshold: 3,
106         })
107         if err != nil {
108                 return fmt.Errorf("vault-init: %s", err)
109         }
110         atomicWriteJSON(path.Join(cfg.DataDir, "vault-keys.json"), resp, 0400)
111         atomicWriteFile(path.Join(cfg.DataDir, "vault-root-token.txt"), []byte(resp.RootToken), 0400)
112         vault.SetToken(resp.RootToken)
113
114         ok := false
115         for _, key := range resp.Keys {
116                 resp, err := vault.Sys().Unseal(key)
117                 if err != nil {
118                         log.Printf("error: unseal: %s", err)
119                         continue
120                 }
121                 if !resp.Sealed {
122                         log.Printf("unseal successful")
123                         ok = true
124                         break
125                 }
126         }
127         if !ok {
128                 return fmt.Errorf("vault unseal failed!")
129         }
130
131         master, err := consul.master(ctx)
132         if err != nil {
133                 return err
134         }
135         token, _, err := master.ACL().Create(&consulAPI.ACLEntry{Name: "vault", Type: "management"}, nil)
136         if err != nil {
137                 return err
138         }
139         err = waitCheck(ctx, 30*time.Second, func(context.Context) error {
140                 return vault.Sys().Mount("consul", &api.MountInput{Type: "consul"})
141         })
142         if err != nil {
143                 return err
144         }
145         _, err = vault.Logical().Write("consul/config/access", map[string]interface{}{
146                 "address": fmt.Sprintf("127.0.0.1:%d", cfg.Ports.ConsulHTTP),
147                 "token":   string(token),
148         })
149         if err != nil {
150                 return err
151         }
152         _, err = vault.Logical().Write("consul/roles/write-all", map[string]interface{}{
153                 "policy": base64.StdEncoding.EncodeToString([]byte(`key "" { policy = "write" }`)),
154         })
155         return err
156 }
157
158 func (vb *vaultBooter) client(ctx context.Context) (*api.Client, error) {
159         cfg := cfg(ctx)
160         vaultCfg.Address = fmt.Sprintf("http://0.0.0.0:%d", cfg.Ports.VaultServer)
161         return api.NewClient(vaultCfg)
162 }
163
164 func (vb *vaultBooter) check(ctx context.Context) error {
165         cfg := cfg(ctx)
166         vault, err := vb.client(ctx)
167         if err != nil {
168                 return err
169         }
170         token, err := ioutil.ReadFile(path.Join(cfg.DataDir, "vault-root-token.txt"))
171         if err != nil {
172                 return err
173         }
174         vault.SetToken(string(token))
175         if init, err := vault.Sys().InitStatus(); err != nil {
176                 return err
177         } else if !init {
178                 return fmt.Errorf("vault is not initialized")
179         }
180         return nil
181 }