1package storage 2 3import ( 4 "bytes" 5 "encoding/pem" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "os" 10 "path/filepath" 11 "strings" 12 13 "github.com/sirupsen/logrus" 14 "github.com/theupdateframework/notary" 15) 16 17// NewFileStore creates a fully configurable file store 18func NewFileStore(baseDir, fileExt string) (*FilesystemStore, error) { 19 baseDir = filepath.Clean(baseDir) 20 if err := createDirectory(baseDir, notary.PrivExecPerms); err != nil { 21 return nil, err 22 } 23 if !strings.HasPrefix(fileExt, ".") { 24 fileExt = "." + fileExt 25 } 26 27 return &FilesystemStore{ 28 baseDir: baseDir, 29 ext: fileExt, 30 }, nil 31} 32 33// NewPrivateKeyFileStorage initializes a new filestore for private keys, appending 34// the notary.PrivDir to the baseDir. 35func NewPrivateKeyFileStorage(baseDir, fileExt string) (*FilesystemStore, error) { 36 baseDir = filepath.Join(baseDir, notary.PrivDir) 37 myStore, err := NewFileStore(baseDir, fileExt) 38 myStore.migrateTo0Dot4() 39 return myStore, err 40} 41 42// NewPrivateSimpleFileStore is a wrapper to create an owner readable/writeable 43// _only_ filestore 44func NewPrivateSimpleFileStore(baseDir, fileExt string) (*FilesystemStore, error) { 45 return NewFileStore(baseDir, fileExt) 46} 47 48// FilesystemStore is a store in a locally accessible directory 49type FilesystemStore struct { 50 baseDir string 51 ext string 52} 53 54func (f *FilesystemStore) moveKeyTo0Dot4Location(file string) { 55 keyID := filepath.Base(file) 56 fileDir := filepath.Dir(file) 57 d, _ := f.Get(file) 58 block, _ := pem.Decode(d) 59 if block == nil { 60 logrus.Warn("Key data for", file, "could not be decoded as a valid PEM block. The key will not been migrated and may not be available") 61 return 62 } 63 fileDir = strings.TrimPrefix(fileDir, notary.RootKeysSubdir) 64 fileDir = strings.TrimPrefix(fileDir, notary.NonRootKeysSubdir) 65 if fileDir != "" { 66 block.Headers["gun"] = filepath.ToSlash(fileDir[1:]) 67 } 68 if strings.Contains(keyID, "_") { 69 role := strings.Split(keyID, "_")[1] 70 keyID = strings.TrimSuffix(keyID, "_"+role) 71 block.Headers["role"] = role 72 } 73 var keyPEM bytes.Buffer 74 // since block came from decoding the PEM bytes in the first place, and all we're doing is adding some headers we ignore the possibility of an error while encoding the block 75 pem.Encode(&keyPEM, block) 76 f.Set(keyID, keyPEM.Bytes()) 77} 78 79func (f *FilesystemStore) migrateTo0Dot4() { 80 rootKeysSubDir := filepath.Clean(filepath.Join(f.Location(), notary.RootKeysSubdir)) 81 nonRootKeysSubDir := filepath.Clean(filepath.Join(f.Location(), notary.NonRootKeysSubdir)) 82 if _, err := os.Stat(rootKeysSubDir); !os.IsNotExist(err) && f.Location() != rootKeysSubDir { 83 if rootKeysSubDir == "" || rootKeysSubDir == "/" { 84 // making sure we don't remove a user's homedir 85 logrus.Warn("The directory for root keys is an unsafe value, we are not going to delete the directory. Please delete it manually") 86 } else { 87 // root_keys exists, migrate things from it 88 listOnlyRootKeysDirStore, _ := NewFileStore(rootKeysSubDir, f.ext) 89 for _, file := range listOnlyRootKeysDirStore.ListFiles() { 90 f.moveKeyTo0Dot4Location(filepath.Join(notary.RootKeysSubdir, file)) 91 } 92 // delete the old directory 93 os.RemoveAll(rootKeysSubDir) 94 } 95 } 96 97 if _, err := os.Stat(nonRootKeysSubDir); !os.IsNotExist(err) && f.Location() != nonRootKeysSubDir { 98 if nonRootKeysSubDir == "" || nonRootKeysSubDir == "/" { 99 // making sure we don't remove a user's homedir 100 logrus.Warn("The directory for non root keys is an unsafe value, we are not going to delete the directory. Please delete it manually") 101 } else { 102 // tuf_keys exists, migrate things from it 103 listOnlyNonRootKeysDirStore, _ := NewFileStore(nonRootKeysSubDir, f.ext) 104 for _, file := range listOnlyNonRootKeysDirStore.ListFiles() { 105 f.moveKeyTo0Dot4Location(filepath.Join(notary.NonRootKeysSubdir, file)) 106 } 107 // delete the old directory 108 os.RemoveAll(nonRootKeysSubDir) 109 } 110 } 111 112 // if we have a trusted_certificates folder, let's delete for a complete migration since it is unused by new clients 113 certsSubDir := filepath.Join(f.Location(), "trusted_certificates") 114 if certsSubDir == "" || certsSubDir == "/" { 115 logrus.Warn("The directory for trusted certificate is an unsafe value, we are not going to delete the directory. Please delete it manually") 116 } else { 117 os.RemoveAll(certsSubDir) 118 } 119} 120 121func (f *FilesystemStore) getPath(name string) (string, error) { 122 fileName := fmt.Sprintf("%s%s", name, f.ext) 123 fullPath := filepath.Join(f.baseDir, fileName) 124 125 if !strings.HasPrefix(fullPath, f.baseDir) { 126 return "", ErrPathOutsideStore 127 } 128 return fullPath, nil 129} 130 131// GetSized returns the meta for the given name (a role) up to size bytes 132// If size is "NoSizeLimit", this corresponds to "infinite," but we cut off at a 133// predefined threshold "notary.MaxDownloadSize". If the file is larger than size 134// we return ErrMaliciousServer for consistency with the HTTPStore 135func (f *FilesystemStore) GetSized(name string, size int64) ([]byte, error) { 136 p, err := f.getPath(name) 137 if err != nil { 138 return nil, err 139 } 140 file, err := os.OpenFile(p, os.O_RDONLY, notary.PrivNoExecPerms) 141 if err != nil { 142 if os.IsNotExist(err) { 143 err = ErrMetaNotFound{Resource: name} 144 } 145 return nil, err 146 } 147 defer file.Close() 148 149 if size == NoSizeLimit { 150 size = notary.MaxDownloadSize 151 } 152 153 stat, err := file.Stat() 154 if err != nil { 155 return nil, err 156 } 157 if stat.Size() > size { 158 return nil, ErrMaliciousServer{} 159 } 160 161 l := io.LimitReader(file, size) 162 return ioutil.ReadAll(l) 163} 164 165// Get returns the meta for the given name. 166func (f *FilesystemStore) Get(name string) ([]byte, error) { 167 p, err := f.getPath(name) 168 if err != nil { 169 return nil, err 170 } 171 meta, err := ioutil.ReadFile(p) 172 if err != nil { 173 if os.IsNotExist(err) { 174 err = ErrMetaNotFound{Resource: name} 175 } 176 return nil, err 177 } 178 return meta, nil 179} 180 181// SetMulti sets the metadata for multiple roles in one operation 182func (f *FilesystemStore) SetMulti(metas map[string][]byte) error { 183 for role, blob := range metas { 184 err := f.Set(role, blob) 185 if err != nil { 186 return err 187 } 188 } 189 return nil 190} 191 192// Set sets the meta for a single role 193func (f *FilesystemStore) Set(name string, meta []byte) error { 194 fp, err := f.getPath(name) 195 if err != nil { 196 return err 197 } 198 199 // Ensures the parent directories of the file we are about to write exist 200 err = os.MkdirAll(filepath.Dir(fp), notary.PrivExecPerms) 201 if err != nil { 202 return err 203 } 204 205 // if something already exists, just delete it and re-write it 206 os.RemoveAll(fp) 207 208 // Write the file to disk 209 return ioutil.WriteFile(fp, meta, notary.PrivNoExecPerms) 210} 211 212// RemoveAll clears the existing filestore by removing its base directory 213func (f *FilesystemStore) RemoveAll() error { 214 return os.RemoveAll(f.baseDir) 215} 216 217// Remove removes the metadata for a single role - if the metadata doesn't 218// exist, no error is returned 219func (f *FilesystemStore) Remove(name string) error { 220 p, err := f.getPath(name) 221 if err != nil { 222 return err 223 } 224 return os.RemoveAll(p) // RemoveAll succeeds if path doesn't exist 225} 226 227// Location returns a human readable name for the storage location 228func (f FilesystemStore) Location() string { 229 return f.baseDir 230} 231 232// ListFiles returns a list of all the filenames that can be used with Get* 233// to retrieve content from this filestore 234func (f FilesystemStore) ListFiles() []string { 235 files := make([]string, 0, 0) 236 filepath.Walk(f.baseDir, func(fp string, fi os.FileInfo, err error) error { 237 // If there are errors, ignore this particular file 238 if err != nil { 239 return nil 240 } 241 // Ignore if it is a directory 242 if fi.IsDir() { 243 return nil 244 } 245 246 // If this is a symlink, ignore it 247 if fi.Mode()&os.ModeSymlink == os.ModeSymlink { 248 return nil 249 } 250 251 // Only allow matches that end with our certificate extension (e.g. *.crt) 252 matched, _ := filepath.Match("*"+f.ext, fi.Name()) 253 254 if matched { 255 // Find the relative path for this file relative to the base path. 256 fp, err = filepath.Rel(f.baseDir, fp) 257 if err != nil { 258 return err 259 } 260 trimmed := strings.TrimSuffix(fp, f.ext) 261 files = append(files, trimmed) 262 } 263 return nil 264 }) 265 return files 266} 267 268// createDirectory receives a string of the path to a directory. 269// It does not support passing files, so the caller has to remove 270// the filename by doing filepath.Dir(full_path_to_file) 271func createDirectory(dir string, perms os.FileMode) error { 272 // This prevents someone passing /path/to/dir and 'dir' not being created 273 // If two '//' exist, MkdirAll deals it with correctly 274 dir = dir + "/" 275 return os.MkdirAll(dir, perms) 276} 277