blob: 2ffd180c25a8bfc78fb58e8ad506103a164bd492 [file] [log] [blame]
Lorenz Brunf95909d2019-09-11 19:48:26 +02001// Copyright 2020 The Monogon Project Authors.
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17package storage
18
Lorenz Brundd8c80e2019-10-07 16:19:49 +020019import (
20 "fmt"
21 "io/ioutil"
22 "os"
23 "os/exec"
24 "path/filepath"
25 "smalltown/internal/common"
26 "smalltown/pkg/tpm"
27 "sync"
28
29 "go.uber.org/zap"
30 "golang.org/x/sys/unix"
31)
32
33const (
34 dataMountPath = "/data"
35 espMountPath = "/esp"
36 espDataPath = espMountPath + "/EFI/smalltown"
37 etcdSealedKeyLocation = espDataPath + "/data-key.bin"
38)
39
40type Manager struct {
41 logger *zap.Logger
42 dataReady bool
43 initializationError error
44 mutex sync.RWMutex
45}
46
47func Initialize(logger *zap.Logger) (*Manager, error) {
48 if err := FindPartitions(); err != nil {
49 return nil, err
50 }
51
52 if err := os.Mkdir("/esp", 0755); err != nil {
53 return nil, err
54 }
55
56 // We're mounting ESP sync for reliability, this lowers our chances of getting half-written files
57 if err := unix.Mount(ESPDevicePath, espMountPath, "vfat", unix.MS_NOEXEC|unix.MS_NODEV|unix.MS_SYNC, ""); err != nil {
58 return nil, err
59 }
60
61 manager := &Manager{
62 logger: logger,
63 dataReady: false,
64 }
65
66 manager.mutex.Lock()
67 defer manager.mutex.Unlock()
68
69 sealedKeyFile, err := os.Open(etcdSealedKeyLocation)
70 if os.IsNotExist(err) {
71 logger.Info("Initializing encrypted storage, this might take a while...")
72 go manager.initializeData()
73 } else if err != nil {
74 return nil, err
75 } else {
76 sealedKey, err := ioutil.ReadAll(sealedKeyFile)
77 sealedKeyFile.Close()
78 if err != nil {
79 return nil, err
80 }
81 key, err := tpm.Unseal(sealedKey)
82 if err != nil {
83 return nil, err
84 }
85 if err := MapEncryptedBlockDevice("data", SmalltownDataCryptPath, key); err != nil {
86 return nil, err
87 }
88 if err := manager.mountData(); err != nil {
89 return nil, err
90 }
91 logger.Info("Mounted encrypted storage")
92 }
93 return manager, nil
94}
95
96func (s *Manager) initializeData() {
97 key, err := tpm.GenerateSafeKey(256 / 8)
98 if err != nil {
99 s.logger.Error("Failed to generate master key", zap.Error(err))
100 s.initializationError = fmt.Errorf("Failed to generate master key: %w", err)
101 return
102 }
103 sealedKey, err := tpm.Seal(key, tpm.FullSystemPCRs)
104 if err != nil {
105 s.logger.Error("Failed to seal master key", zap.Error(err))
106 s.initializationError = fmt.Errorf("Failed to seal master key: %w", err)
107 return
108 }
109 if err := InitializeEncryptedBlockDevice("data", SmalltownDataCryptPath, key); err != nil {
110 s.logger.Error("Failed to initialize encrypted block device", zap.Error(err))
111 s.initializationError = fmt.Errorf("Failed to initialize encrypted block device: %w", err)
112 return
113 }
114 mkfsCmd := exec.Command("/bin/mkfs.xfs", "-qf", "/dev/data")
115 if _, err := mkfsCmd.Output(); err != nil {
116 s.logger.Error("Failed to format encrypted block device", zap.Error(err))
117 s.initializationError = fmt.Errorf("Failed to format encrypted block device: %w", err)
118 return
119 }
120 // This file is the marker if the partition has
121 if err := ioutil.WriteFile(etcdSealedKeyLocation, sealedKey, 0600); err != nil {
122 panic(err)
123 }
124
125 if err := s.mountData(); err != nil {
126 s.initializationError = err
127 return
128 }
129
130 s.mutex.Lock()
131 s.dataReady = true
132 s.mutex.Unlock()
133
134 s.logger.Info("Initialized encrypted storage")
135}
136
137func (s *Manager) mountData() error {
138 if err := os.Mkdir("/data", 0755); err != nil {
139 return err
140 }
141
142 if err := unix.Mount("/dev/data", "/data", "xfs", unix.MS_NOEXEC|unix.MS_NODEV, ""); err != nil {
143 return err
144 }
145 return nil
146}
147
148// GetPathInPlace returns a path in the given place
149// It may return ErrNotInitialized if the place you're trying to access
150// is not initialized or ErrUnknownPlace if the place is not known
151func (s *Manager) GetPathInPlace(place common.DataPlace, path string) (string, error) {
152 s.mutex.RLock()
153 defer s.mutex.RUnlock()
154 switch place {
155 case common.PlaceESP:
156 return filepath.Join(espDataPath, path), nil
157 case common.PlaceData:
158 if s.dataReady {
159 return filepath.Join(dataMountPath, path), nil
160 }
161 return "", common.ErrNotInitialized
162 default:
163 return "", common.ErrUnknownPlace
164 }
165}