You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

595 lines
18 KiB

  1. // Copyright (c) 2015-2021 MinIO, Inc.
  2. //
  3. // This file is part of MinIO Object Storage stack
  4. //
  5. // This program is free software: you can redistribute it and/or modify
  6. // it under the terms of the GNU Affero General Public License as published by
  7. // the Free Software Foundation, either version 3 of the License, or
  8. // (at your option) any later version.
  9. //
  10. // This program is distributed in the hope that it will be useful
  11. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. // GNU Affero General Public License for more details.
  14. //
  15. // You should have received a copy of the GNU Affero General Public License
  16. // along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. package cmd
  18. import (
  19. "bytes"
  20. "context"
  21. "crypto/rand"
  22. "encoding/binary"
  23. "encoding/json"
  24. "encoding/xml"
  25. "errors"
  26. "fmt"
  27. "path"
  28. "time"
  29. "github.com/minio/madmin-go/v3"
  30. "github.com/minio/minio-go/v7/pkg/tags"
  31. bucketsse "github.com/minio/minio/internal/bucket/encryption"
  32. "github.com/minio/minio/internal/bucket/lifecycle"
  33. objectlock "github.com/minio/minio/internal/bucket/object/lock"
  34. "github.com/minio/minio/internal/bucket/replication"
  35. "github.com/minio/minio/internal/bucket/versioning"
  36. "github.com/minio/minio/internal/crypto"
  37. "github.com/minio/minio/internal/event"
  38. "github.com/minio/minio/internal/fips"
  39. "github.com/minio/minio/internal/kms"
  40. "github.com/minio/minio/internal/logger"
  41. "github.com/minio/pkg/v3/policy"
  42. "github.com/minio/sio"
  43. )
  44. const (
  45. legacyBucketObjectLockEnabledConfigFile = "object-lock-enabled.json"
  46. legacyBucketObjectLockEnabledConfig = `{"x-amz-bucket-object-lock-enabled":true}`
  47. bucketMetadataFile = ".metadata.bin"
  48. bucketMetadataFormat = 1
  49. bucketMetadataVersion = 1
  50. )
  51. var (
  52. enabledBucketObjectLockConfig = []byte(`<ObjectLockConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><ObjectLockEnabled>Enabled</ObjectLockEnabled></ObjectLockConfiguration>`)
  53. enabledBucketVersioningConfig = []byte(`<VersioningConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Status>Enabled</Status></VersioningConfiguration>`)
  54. )
  55. //go:generate msgp -file $GOFILE
  56. // BucketMetadata contains bucket metadata.
  57. // When adding/removing fields, regenerate the marshal code using the go generate above.
  58. // Only changing meaning of fields requires a version bump.
  59. // bucketMetadataFormat refers to the format.
  60. // bucketMetadataVersion can be used to track a rolling upgrade of a field.
  61. type BucketMetadata struct {
  62. Name string
  63. Created time.Time
  64. LockEnabled bool // legacy not used anymore.
  65. PolicyConfigJSON []byte
  66. NotificationConfigXML []byte
  67. LifecycleConfigXML []byte
  68. ObjectLockConfigXML []byte
  69. VersioningConfigXML []byte
  70. EncryptionConfigXML []byte
  71. TaggingConfigXML []byte
  72. QuotaConfigJSON []byte
  73. ReplicationConfigXML []byte
  74. BucketTargetsConfigJSON []byte
  75. BucketTargetsConfigMetaJSON []byte
  76. PolicyConfigUpdatedAt time.Time
  77. ObjectLockConfigUpdatedAt time.Time
  78. EncryptionConfigUpdatedAt time.Time
  79. TaggingConfigUpdatedAt time.Time
  80. QuotaConfigUpdatedAt time.Time
  81. ReplicationConfigUpdatedAt time.Time
  82. VersioningConfigUpdatedAt time.Time
  83. LifecycleConfigUpdatedAt time.Time
  84. NotificationConfigUpdatedAt time.Time
  85. BucketTargetsConfigUpdatedAt time.Time
  86. BucketTargetsConfigMetaUpdatedAt time.Time
  87. // Add a new UpdatedAt field and update lastUpdate function
  88. // Unexported fields. Must be updated atomically.
  89. policyConfig *policy.BucketPolicy
  90. notificationConfig *event.Config
  91. lifecycleConfig *lifecycle.Lifecycle
  92. objectLockConfig *objectlock.Config
  93. versioningConfig *versioning.Versioning
  94. sseConfig *bucketsse.BucketSSEConfig
  95. taggingConfig *tags.Tags
  96. quotaConfig *madmin.BucketQuota
  97. replicationConfig *replication.Config
  98. bucketTargetConfig *madmin.BucketTargets
  99. bucketTargetConfigMeta map[string]string
  100. }
  101. // newBucketMetadata creates BucketMetadata with the supplied name and Created to Now.
  102. func newBucketMetadata(name string) BucketMetadata {
  103. return BucketMetadata{
  104. Name: name,
  105. notificationConfig: &event.Config{
  106. XMLNS: "http://s3.amazonaws.com/doc/2006-03-01/",
  107. },
  108. quotaConfig: &madmin.BucketQuota{},
  109. versioningConfig: &versioning.Versioning{
  110. XMLNS: "http://s3.amazonaws.com/doc/2006-03-01/",
  111. },
  112. bucketTargetConfig: &madmin.BucketTargets{},
  113. bucketTargetConfigMeta: make(map[string]string),
  114. }
  115. }
  116. // Return the last update of this bucket metadata, which
  117. // means, the last update of any policy document.
  118. func (b BucketMetadata) lastUpdate() (t time.Time) {
  119. if b.PolicyConfigUpdatedAt.After(t) {
  120. t = b.PolicyConfigUpdatedAt
  121. }
  122. if b.ObjectLockConfigUpdatedAt.After(t) {
  123. t = b.ObjectLockConfigUpdatedAt
  124. }
  125. if b.EncryptionConfigUpdatedAt.After(t) {
  126. t = b.EncryptionConfigUpdatedAt
  127. }
  128. if b.TaggingConfigUpdatedAt.After(t) {
  129. t = b.TaggingConfigUpdatedAt
  130. }
  131. if b.QuotaConfigUpdatedAt.After(t) {
  132. t = b.QuotaConfigUpdatedAt
  133. }
  134. if b.ReplicationConfigUpdatedAt.After(t) {
  135. t = b.ReplicationConfigUpdatedAt
  136. }
  137. if b.VersioningConfigUpdatedAt.After(t) {
  138. t = b.VersioningConfigUpdatedAt
  139. }
  140. if b.LifecycleConfigUpdatedAt.After(t) {
  141. t = b.LifecycleConfigUpdatedAt
  142. }
  143. if b.NotificationConfigUpdatedAt.After(t) {
  144. t = b.NotificationConfigUpdatedAt
  145. }
  146. if b.BucketTargetsConfigUpdatedAt.After(t) {
  147. t = b.BucketTargetsConfigUpdatedAt
  148. }
  149. if b.BucketTargetsConfigMetaUpdatedAt.After(t) {
  150. t = b.BucketTargetsConfigMetaUpdatedAt
  151. }
  152. return
  153. }
  154. // Versioning returns true if versioning is enabled
  155. func (b BucketMetadata) Versioning() bool {
  156. return b.LockEnabled || (b.versioningConfig != nil && b.versioningConfig.Enabled()) || (b.objectLockConfig != nil && b.objectLockConfig.Enabled())
  157. }
  158. // ObjectLocking returns true if object locking is enabled
  159. func (b BucketMetadata) ObjectLocking() bool {
  160. return b.LockEnabled || (b.objectLockConfig != nil && b.objectLockConfig.Enabled())
  161. }
  162. // SetCreatedAt preserves the CreatedAt time for bucket across sites in site replication. It defaults to
  163. // creation time of bucket on this cluster in all other cases.
  164. func (b *BucketMetadata) SetCreatedAt(createdAt time.Time) {
  165. if b.Created.IsZero() {
  166. b.Created = UTCNow()
  167. }
  168. if !createdAt.IsZero() {
  169. b.Created = createdAt.UTC()
  170. }
  171. }
  172. // Load - loads the metadata of bucket by name from ObjectLayer api.
  173. // If an error is returned the returned metadata will be default initialized.
  174. func readBucketMetadata(ctx context.Context, api ObjectLayer, name string) (BucketMetadata, error) {
  175. if name == "" {
  176. internalLogIf(ctx, errors.New("bucket name cannot be empty"), logger.WarningKind)
  177. return BucketMetadata{}, errInvalidArgument
  178. }
  179. b := newBucketMetadata(name)
  180. configFile := path.Join(bucketMetaPrefix, name, bucketMetadataFile)
  181. data, err := readConfig(ctx, api, configFile)
  182. if err != nil {
  183. return b, err
  184. }
  185. if len(data) <= 4 {
  186. return b, fmt.Errorf("loadBucketMetadata: no data")
  187. }
  188. // Read header
  189. switch binary.LittleEndian.Uint16(data[0:2]) {
  190. case bucketMetadataFormat:
  191. default:
  192. return b, fmt.Errorf("loadBucketMetadata: unknown format: %d", binary.LittleEndian.Uint16(data[0:2]))
  193. }
  194. switch binary.LittleEndian.Uint16(data[2:4]) {
  195. case bucketMetadataVersion:
  196. default:
  197. return b, fmt.Errorf("loadBucketMetadata: unknown version: %d", binary.LittleEndian.Uint16(data[2:4]))
  198. }
  199. _, err = b.UnmarshalMsg(data[4:])
  200. return b, err
  201. }
  202. func loadBucketMetadataParse(ctx context.Context, objectAPI ObjectLayer, bucket string, parse bool) (BucketMetadata, error) {
  203. b, err := readBucketMetadata(ctx, objectAPI, bucket)
  204. b.Name = bucket // in-case parsing failed for some reason, make sure bucket name is not empty.
  205. if err != nil && !errors.Is(err, errConfigNotFound) {
  206. return b, err
  207. }
  208. if err == nil {
  209. b.defaultTimestamps()
  210. }
  211. // If bucket metadata is missing look for legacy files,
  212. // since we only ever had b.Created as non-zero when
  213. // migration was complete in 2020-May release. So this
  214. // a check to avoid migrating for buckets that already
  215. // have this field set.
  216. if b.Created.IsZero() {
  217. configs, err := b.getAllLegacyConfigs(ctx, objectAPI)
  218. if err != nil {
  219. return b, err
  220. }
  221. if len(configs) > 0 {
  222. // Old bucket without bucket metadata. Hence we migrate existing settings.
  223. if err = b.convertLegacyConfigs(ctx, objectAPI, configs); err != nil {
  224. return b, err
  225. }
  226. }
  227. }
  228. if parse {
  229. // nothing to update, parse and proceed.
  230. if err = b.parseAllConfigs(ctx, objectAPI); err != nil {
  231. return b, err
  232. }
  233. }
  234. // migrate unencrypted remote targets
  235. if err = b.migrateTargetConfig(ctx, objectAPI); err != nil {
  236. return b, err
  237. }
  238. return b, nil
  239. }
  240. // loadBucketMetadata loads and migrates to bucket metadata.
  241. func loadBucketMetadata(ctx context.Context, objectAPI ObjectLayer, bucket string) (BucketMetadata, error) {
  242. return loadBucketMetadataParse(ctx, objectAPI, bucket, true)
  243. }
  244. // parseAllConfigs will parse all configs and populate the private fields.
  245. // The first error encountered is returned.
  246. func (b *BucketMetadata) parseAllConfigs(ctx context.Context, objectAPI ObjectLayer) (err error) {
  247. if len(b.PolicyConfigJSON) != 0 {
  248. b.policyConfig, err = policy.ParseBucketPolicyConfig(bytes.NewReader(b.PolicyConfigJSON), b.Name)
  249. if err != nil {
  250. return err
  251. }
  252. } else {
  253. b.policyConfig = nil
  254. }
  255. if len(b.NotificationConfigXML) != 0 {
  256. if err = xml.Unmarshal(b.NotificationConfigXML, b.notificationConfig); err != nil {
  257. return err
  258. }
  259. }
  260. if len(b.LifecycleConfigXML) != 0 {
  261. b.lifecycleConfig, err = lifecycle.ParseLifecycleConfig(bytes.NewReader(b.LifecycleConfigXML))
  262. if err != nil {
  263. return err
  264. }
  265. } else {
  266. b.lifecycleConfig = nil
  267. }
  268. if len(b.EncryptionConfigXML) != 0 {
  269. b.sseConfig, err = bucketsse.ParseBucketSSEConfig(bytes.NewReader(b.EncryptionConfigXML))
  270. if err != nil {
  271. return err
  272. }
  273. } else {
  274. b.sseConfig = nil
  275. }
  276. if len(b.TaggingConfigXML) != 0 {
  277. b.taggingConfig, err = tags.ParseBucketXML(bytes.NewReader(b.TaggingConfigXML))
  278. if err != nil {
  279. return err
  280. }
  281. } else {
  282. b.taggingConfig = nil
  283. }
  284. if bytes.Equal(b.ObjectLockConfigXML, enabledBucketObjectLockConfig) {
  285. b.VersioningConfigXML = enabledBucketVersioningConfig
  286. }
  287. if len(b.ObjectLockConfigXML) != 0 {
  288. b.objectLockConfig, err = objectlock.ParseObjectLockConfig(bytes.NewReader(b.ObjectLockConfigXML))
  289. if err != nil {
  290. return err
  291. }
  292. } else {
  293. b.objectLockConfig = nil
  294. }
  295. if len(b.VersioningConfigXML) != 0 {
  296. b.versioningConfig, err = versioning.ParseConfig(bytes.NewReader(b.VersioningConfigXML))
  297. if err != nil {
  298. return err
  299. }
  300. }
  301. if len(b.QuotaConfigJSON) != 0 {
  302. b.quotaConfig, err = parseBucketQuota(b.Name, b.QuotaConfigJSON)
  303. if err != nil {
  304. return err
  305. }
  306. }
  307. if len(b.ReplicationConfigXML) != 0 {
  308. b.replicationConfig, err = replication.ParseConfig(bytes.NewReader(b.ReplicationConfigXML))
  309. if err != nil {
  310. return err
  311. }
  312. } else {
  313. b.replicationConfig = nil
  314. }
  315. if len(b.BucketTargetsConfigJSON) != 0 {
  316. b.bucketTargetConfig, err = parseBucketTargetConfig(b.Name, b.BucketTargetsConfigJSON, b.BucketTargetsConfigMetaJSON)
  317. if err != nil {
  318. return err
  319. }
  320. } else {
  321. b.bucketTargetConfig = &madmin.BucketTargets{}
  322. }
  323. return nil
  324. }
  325. func (b *BucketMetadata) getAllLegacyConfigs(ctx context.Context, objectAPI ObjectLayer) (map[string][]byte, error) {
  326. legacyConfigs := []string{
  327. legacyBucketObjectLockEnabledConfigFile,
  328. bucketPolicyConfig,
  329. bucketNotificationConfig,
  330. bucketLifecycleConfig,
  331. bucketQuotaConfigFile,
  332. bucketSSEConfig,
  333. bucketTaggingConfig,
  334. bucketReplicationConfig,
  335. bucketTargetsFile,
  336. objectLockConfig,
  337. }
  338. configs := make(map[string][]byte, len(legacyConfigs))
  339. // Handle migration from lockEnabled to newer format.
  340. if b.LockEnabled {
  341. configs[objectLockConfig] = enabledBucketObjectLockConfig
  342. b.LockEnabled = false // legacy value unset it
  343. // we are only interested in b.ObjectLockConfigXML or objectLockConfig value
  344. }
  345. for _, legacyFile := range legacyConfigs {
  346. configFile := path.Join(bucketMetaPrefix, b.Name, legacyFile)
  347. configData, info, err := readConfigWithMetadata(ctx, objectAPI, configFile, ObjectOptions{})
  348. if err != nil {
  349. if _, ok := err.(ObjectExistsAsDirectory); ok {
  350. // in FS mode it possible that we have actual
  351. // files in this folder with `.minio.sys/buckets/bucket/configFile`
  352. continue
  353. }
  354. if errors.Is(err, errConfigNotFound) {
  355. // legacy file config not found, proceed to look for new metadata.
  356. continue
  357. }
  358. return nil, err
  359. }
  360. configs[legacyFile] = configData
  361. b.Created = info.ModTime
  362. }
  363. return configs, nil
  364. }
  365. func (b *BucketMetadata) convertLegacyConfigs(ctx context.Context, objectAPI ObjectLayer, configs map[string][]byte) error {
  366. for legacyFile, configData := range configs {
  367. switch legacyFile {
  368. case legacyBucketObjectLockEnabledConfigFile:
  369. if string(configData) == legacyBucketObjectLockEnabledConfig {
  370. b.ObjectLockConfigXML = enabledBucketObjectLockConfig
  371. b.VersioningConfigXML = enabledBucketVersioningConfig
  372. b.LockEnabled = false // legacy value unset it
  373. // we are only interested in b.ObjectLockConfigXML
  374. }
  375. case bucketPolicyConfig:
  376. b.PolicyConfigJSON = configData
  377. case bucketNotificationConfig:
  378. b.NotificationConfigXML = configData
  379. case bucketLifecycleConfig:
  380. b.LifecycleConfigXML = configData
  381. case bucketSSEConfig:
  382. b.EncryptionConfigXML = configData
  383. case bucketTaggingConfig:
  384. b.TaggingConfigXML = configData
  385. case objectLockConfig:
  386. b.ObjectLockConfigXML = configData
  387. b.VersioningConfigXML = enabledBucketVersioningConfig
  388. case bucketQuotaConfigFile:
  389. b.QuotaConfigJSON = configData
  390. case bucketReplicationConfig:
  391. b.ReplicationConfigXML = configData
  392. case bucketTargetsFile:
  393. b.BucketTargetsConfigJSON = configData
  394. }
  395. }
  396. b.defaultTimestamps()
  397. if err := b.Save(ctx, objectAPI); err != nil {
  398. return err
  399. }
  400. for legacyFile := range configs {
  401. configFile := path.Join(bucketMetaPrefix, b.Name, legacyFile)
  402. if err := deleteConfig(ctx, objectAPI, configFile); err != nil && !errors.Is(err, errConfigNotFound) {
  403. internalLogIf(ctx, err, logger.WarningKind)
  404. }
  405. }
  406. return nil
  407. }
  408. // default timestamps to metadata Created timestamp if unset.
  409. func (b *BucketMetadata) defaultTimestamps() {
  410. if b.PolicyConfigUpdatedAt.IsZero() {
  411. b.PolicyConfigUpdatedAt = b.Created
  412. }
  413. if b.EncryptionConfigUpdatedAt.IsZero() {
  414. b.EncryptionConfigUpdatedAt = b.Created
  415. }
  416. if b.TaggingConfigUpdatedAt.IsZero() {
  417. b.TaggingConfigUpdatedAt = b.Created
  418. }
  419. if b.ObjectLockConfigUpdatedAt.IsZero() {
  420. b.ObjectLockConfigUpdatedAt = b.Created
  421. }
  422. if b.QuotaConfigUpdatedAt.IsZero() {
  423. b.QuotaConfigUpdatedAt = b.Created
  424. }
  425. if b.ReplicationConfigUpdatedAt.IsZero() {
  426. b.ReplicationConfigUpdatedAt = b.Created
  427. }
  428. if b.VersioningConfigUpdatedAt.IsZero() {
  429. b.VersioningConfigUpdatedAt = b.Created
  430. }
  431. if b.LifecycleConfigUpdatedAt.IsZero() {
  432. b.LifecycleConfigUpdatedAt = b.Created
  433. }
  434. if b.NotificationConfigUpdatedAt.IsZero() {
  435. b.NotificationConfigUpdatedAt = b.Created
  436. }
  437. if b.BucketTargetsConfigUpdatedAt.IsZero() {
  438. b.BucketTargetsConfigUpdatedAt = b.Created
  439. }
  440. if b.BucketTargetsConfigMetaUpdatedAt.IsZero() {
  441. b.BucketTargetsConfigMetaUpdatedAt = b.Created
  442. }
  443. }
  444. // Save config to supplied ObjectLayer api.
  445. func (b *BucketMetadata) Save(ctx context.Context, api ObjectLayer) error {
  446. if err := b.parseAllConfigs(ctx, api); err != nil {
  447. return err
  448. }
  449. data := make([]byte, 4, b.Msgsize()+4)
  450. // Initialize the header.
  451. binary.LittleEndian.PutUint16(data[0:2], bucketMetadataFormat)
  452. binary.LittleEndian.PutUint16(data[2:4], bucketMetadataVersion)
  453. // Marshal the bucket metadata
  454. data, err := b.MarshalMsg(data)
  455. if err != nil {
  456. return err
  457. }
  458. configFile := path.Join(bucketMetaPrefix, b.Name, bucketMetadataFile)
  459. return saveConfig(ctx, api, configFile, data)
  460. }
  461. // migrate config for remote targets by encrypting data if currently unencrypted and kms is configured.
  462. func (b *BucketMetadata) migrateTargetConfig(ctx context.Context, objectAPI ObjectLayer) error {
  463. var err error
  464. // early return if no targets or already encrypted
  465. if len(b.BucketTargetsConfigJSON) == 0 || GlobalKMS == nil || len(b.BucketTargetsConfigMetaJSON) != 0 {
  466. return nil
  467. }
  468. encBytes, metaBytes, err := encryptBucketMetadata(ctx, b.Name, b.BucketTargetsConfigJSON, kms.Context{b.Name: b.Name, bucketTargetsFile: bucketTargetsFile})
  469. if err != nil {
  470. return err
  471. }
  472. b.BucketTargetsConfigJSON = encBytes
  473. b.BucketTargetsConfigMetaJSON = metaBytes
  474. return b.Save(ctx, objectAPI)
  475. }
  476. // encrypt bucket metadata if kms is configured.
  477. func encryptBucketMetadata(ctx context.Context, bucket string, input []byte, kmsContext kms.Context) (output, metabytes []byte, err error) {
  478. if GlobalKMS == nil {
  479. output = input
  480. return
  481. }
  482. metadata := make(map[string]string)
  483. key, err := GlobalKMS.GenerateKey(ctx, &kms.GenerateKeyRequest{AssociatedData: kmsContext})
  484. if err != nil {
  485. return
  486. }
  487. outbuf := bytes.NewBuffer(nil)
  488. objectKey := crypto.GenerateKey(key.Plaintext, rand.Reader)
  489. sealedKey := objectKey.Seal(key.Plaintext, crypto.GenerateIV(rand.Reader), crypto.S3.String(), bucket, "")
  490. crypto.S3.CreateMetadata(metadata, key.KeyID, key.Ciphertext, sealedKey)
  491. _, err = sio.Encrypt(outbuf, bytes.NewBuffer(input), sio.Config{Key: objectKey[:], MinVersion: sio.Version20, CipherSuites: fips.DARECiphers()})
  492. if err != nil {
  493. return output, metabytes, err
  494. }
  495. metabytes, err = json.Marshal(metadata)
  496. if err != nil {
  497. return
  498. }
  499. return outbuf.Bytes(), metabytes, nil
  500. }
  501. // decrypt bucket metadata if kms is configured.
  502. func decryptBucketMetadata(input []byte, bucket string, meta map[string]string, kmsContext kms.Context) ([]byte, error) {
  503. if GlobalKMS == nil {
  504. return nil, errKMSNotConfigured
  505. }
  506. keyID, kmsKey, sealedKey, err := crypto.S3.ParseMetadata(meta)
  507. if err != nil {
  508. return nil, err
  509. }
  510. extKey, err := GlobalKMS.Decrypt(context.TODO(), &kms.DecryptRequest{
  511. Name: keyID,
  512. Ciphertext: kmsKey,
  513. AssociatedData: kmsContext,
  514. })
  515. if err != nil {
  516. return nil, err
  517. }
  518. var objectKey crypto.ObjectKey
  519. if err = objectKey.Unseal(extKey, sealedKey, crypto.S3.String(), bucket, ""); err != nil {
  520. return nil, err
  521. }
  522. outbuf := bytes.NewBuffer(nil)
  523. _, err = sio.Decrypt(outbuf, bytes.NewBuffer(input), sio.Config{Key: objectKey[:], MinVersion: sio.Version20, CipherSuites: fips.DARECiphers()})
  524. return outbuf.Bytes(), err
  525. }