blob: 80af4c979d70b1a9bea43f55fee3ee487e57631e [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"
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020021 "git.monogon.dev/source/nexantic.git/core/pkg/tpm"
Lorenz Brundd8c80e2019-10-07 16:19:49 +020022 "io/ioutil"
23 "os"
24 "os/exec"
25 "path/filepath"
Lorenz Brundd8c80e2019-10-07 16:19:49 +020026 "sync"
27
28 "go.uber.org/zap"
29 "golang.org/x/sys/unix"
30)
31
32const (
33 dataMountPath = "/data"
34 espMountPath = "/esp"
35 espDataPath = espMountPath + "/EFI/smalltown"
36 etcdSealedKeyLocation = espDataPath + "/data-key.bin"
37)
38
39type Manager struct {
40 logger *zap.Logger
41 dataReady bool
42 initializationError error
43 mutex sync.RWMutex
44}
45
46func Initialize(logger *zap.Logger) (*Manager, error) {
47 if err := FindPartitions(); err != nil {
48 return nil, err
49 }
50
51 if err := os.Mkdir("/esp", 0755); err != nil {
52 return nil, err
53 }
54
55 // We're mounting ESP sync for reliability, this lowers our chances of getting half-written files
56 if err := unix.Mount(ESPDevicePath, espMountPath, "vfat", unix.MS_NOEXEC|unix.MS_NODEV|unix.MS_SYNC, ""); err != nil {
57 return nil, err
58 }
59
60 manager := &Manager{
61 logger: logger,
62 dataReady: false,
63 }
64
65 manager.mutex.Lock()
66 defer manager.mutex.Unlock()
67
68 sealedKeyFile, err := os.Open(etcdSealedKeyLocation)
69 if os.IsNotExist(err) {
70 logger.Info("Initializing encrypted storage, this might take a while...")
71 go manager.initializeData()
72 } else if err != nil {
73 return nil, err
74 } else {
75 sealedKey, err := ioutil.ReadAll(sealedKeyFile)
76 sealedKeyFile.Close()
77 if err != nil {
78 return nil, err
79 }
80 key, err := tpm.Unseal(sealedKey)
81 if err != nil {
82 return nil, err
83 }
84 if err := MapEncryptedBlockDevice("data", SmalltownDataCryptPath, key); err != nil {
85 return nil, err
86 }
87 if err := manager.mountData(); err != nil {
88 return nil, err
89 }
90 logger.Info("Mounted encrypted storage")
91 }
92 return manager, nil
93}
94
95func (s *Manager) initializeData() {
96 key, err := tpm.GenerateSafeKey(256 / 8)
97 if err != nil {
98 s.logger.Error("Failed to generate master key", zap.Error(err))
99 s.initializationError = fmt.Errorf("Failed to generate master key: %w", err)
100 return
101 }
102 sealedKey, err := tpm.Seal(key, tpm.FullSystemPCRs)
103 if err != nil {
104 s.logger.Error("Failed to seal master key", zap.Error(err))
105 s.initializationError = fmt.Errorf("Failed to seal master key: %w", err)
106 return
107 }
108 if err := InitializeEncryptedBlockDevice("data", SmalltownDataCryptPath, key); err != nil {
109 s.logger.Error("Failed to initialize encrypted block device", zap.Error(err))
110 s.initializationError = fmt.Errorf("Failed to initialize encrypted block device: %w", err)
111 return
112 }
113 mkfsCmd := exec.Command("/bin/mkfs.xfs", "-qf", "/dev/data")
114 if _, err := mkfsCmd.Output(); err != nil {
115 s.logger.Error("Failed to format encrypted block device", zap.Error(err))
116 s.initializationError = fmt.Errorf("Failed to format encrypted block device: %w", err)
117 return
118 }
119 // This file is the marker if the partition has
120 if err := ioutil.WriteFile(etcdSealedKeyLocation, sealedKey, 0600); err != nil {
121 panic(err)
122 }
123
124 if err := s.mountData(); err != nil {
125 s.initializationError = err
126 return
127 }
128
129 s.mutex.Lock()
130 s.dataReady = true
131 s.mutex.Unlock()
132
133 s.logger.Info("Initialized encrypted storage")
134}
135
136func (s *Manager) mountData() error {
137 if err := os.Mkdir("/data", 0755); err != nil {
138 return err
139 }
140
141 if err := unix.Mount("/dev/data", "/data", "xfs", unix.MS_NOEXEC|unix.MS_NODEV, ""); err != nil {
142 return err
143 }
144 return nil
145}
146
147// GetPathInPlace returns a path in the given place
148// It may return ErrNotInitialized if the place you're trying to access
149// is not initialized or ErrUnknownPlace if the place is not known
Leopold Schabel68c58752019-11-14 21:00:59 +0100150func (s *Manager) GetPathInPlace(place DataPlace, path string) (string, error) {
Lorenz Brundd8c80e2019-10-07 16:19:49 +0200151 s.mutex.RLock()
152 defer s.mutex.RUnlock()
153 switch place {
Leopold Schabel68c58752019-11-14 21:00:59 +0100154 case PlaceESP:
Lorenz Brundd8c80e2019-10-07 16:19:49 +0200155 return filepath.Join(espDataPath, path), nil
Leopold Schabel68c58752019-11-14 21:00:59 +0100156 case PlaceData:
Lorenz Brundd8c80e2019-10-07 16:19:49 +0200157 if s.dataReady {
158 return filepath.Join(dataMountPath, path), nil
159 }
Leopold Schabel68c58752019-11-14 21:00:59 +0100160 return "", ErrNotInitialized
Lorenz Brundd8c80e2019-10-07 16:19:49 +0200161 default:
Leopold Schabel68c58752019-11-14 21:00:59 +0100162 return "", ErrUnknownPlace
Lorenz Brundd8c80e2019-10-07 16:19:49 +0200163 }
164}