8000 [pull] master from minio:master by pull[bot] · Pull Request #469 · kokizzu/minio · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
---
name: Bug report
about: Create a report to help us improve
about: Report a bug in MinIO (community edition is source-only)
title: ''
labels: community, triage
assignees: ''

---

## NOTE
If this case is urgent, please subscribe to [Subnet](https://min.io/pricing) so that our 24/7 support team may help you faster.
## IMPORTANT NOTES

**Community Edition**: MinIO community edition is now source-only. Install via `go install github.com/minio/minio@latest`

**Feature Requests**: We are no longer accepting feature requests for the community edition. For feature requests and enterprise support, please subscribe to [MinIO Enterprise Support](https://min.io/pricing).

**Urgent Issues**: If this case is urgent or affects production, please subscribe to [SUBNET](https://min.io/pricing) for 24/7 enterprise support.

<!--- Provide a general summary of the issue in the Title above -->

Expand Down
6 changes: 3 additions & 3 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ blank_issues_enabled: false
contact_links:
- name: MinIO Community Support
url: https://slack.min.io
about: Join here for Community Support
- name: MinIO SUBNET Support
about: Community support via Slack - for questions and discussions
- name: MinIO Enterprise Support (SUBNET)
url: https://min.io/pricing
about: Join here for Enterprise Support
about: Enterprise support with SLA - for production deployments and feature requests
20 changes: 0 additions & 20 de 8000 letions .github/ISSUE_TEMPLATE/feature_request.md

This file was deleted.

33 changes: 20 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,28 +16,35 @@ Use the [MinIO Documentation](https://github.com/minio/docs) project to build an

## MinIO is Open Source Software

We designed MinIO as Open Source software for the Open Source software community.
We encourage the community to remix, redesign, and reshare MinIO under the terms of the AGPLv3 license.
We designed MinIO as Open Source software for the Open Source software community. We encourage the community to remix, redesign, and reshare MinIO under the terms of the AGPLv3 license.

All usage of MinIO in your application stack requires validation against AGPLv3 obligations, which include but are not limited to the release of modified code to the community from which you have benefited.
Any commercial/proprietary usage of the AGPLv3 software, including repackaging or reselling services/features, is done at your own risk.
All usage of MinIO in your application stack requires validation against AGPLv3 obligations, which include but are not limited to the release of modified code to the community from which you have benefited. Any commercial/proprietary usage of the AGPLv3 software, including repackaging or reselling services/features, is done at your own risk.

The AGPLv3 provides no obligation by any party to support, maintain, or warranty the original or any modified work.
All support is provided on a best-effort basis through Github and our [Slack](https//slack.min.io) channel, and any member of the community is welcome to contribute and assist others in their usage of the software.

MinIO [AIStor](https://www.min.io/product/aistor) includes enterprise-grade support and licensing for workloads which require commercial or proprietary usage and production-level SLA/SLO-backed support.
For more information, [reach out for a quote](https://min.io/pricing).
MinIO [AIStor](https://www.min.io/product/aistor) includes enterprise-grade support and licensing for workloads which require commercial or proprietary usage and production-level SLA/SLO-backed support. For more information, [reach out for a quote](https://min.io/pricing).

## Legacy Releases
## Source-Only Distribution

MinIO has no planned or scheduled releases for this repository.
While a new release may be cut at any time, there is no timeline for when a subsequent release may occur.
All existing releases remain accessible through Github or at https://dl.min.io/server/minio/release/ .
**Important:** The MinIO community edition is now distributed as source code only. We will no longer provide pre-compiled binary releases for the community version.

The following sections provide instructions for building against the `master` branch for deployments that require access to the latest changes:
### Installing Latest MinIO Community Edition

- [Install from Source](#install-from-source)
- [Build Docker Image](#build-docker-image)
To use MinIO community edition, you have two options:

1. **Install from source** using `go install github.com/minio/minio@latest` (recommended)
2. **Build a Docker image** from the provided Dockerfile

See the sections below for detailed instructions on each method.

### Legacy Binary Releases

Historical pre-compiled binary releases remain available for reference but are no longer maintained:
- GitHub Releases: https://github.com/minio/minio/releases
- Direct downloads: https://dl.min.io/server/minio/release/

**These legacy binaries will not receive updates.** We strongly recommend using source builds for access to the latest features, bug fixes, and security updates.

## Install from Source

Expand Down
104 changes: 104 additions & 0 deletions cmd/admin-handlers-users_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,8 @@ func TestIAMInternalIDPServerSuite(t *testing.T) {
suite.TestGroupAddRemove(c)
suite.TestServiceAccountOpsByAdmin(c)
suite.TestServiceAccountPrivilegeEscalationBug(c)
suite.TestServiceAccountPrivilegeEscalationBug2_2025_10_15(c, true)
suite.TestServiceAccountPrivilegeEscalationBug2_2025_10_15(c, false)
suite.TestServiceAccountOpsByUser(c)
suite.TestServiceAccountDurationSecondsCondition(c)
suite.TestAddServiceAccountPerms(c)
Expand Down Expand Up @@ -1249,6 +1251,108 @@ func (s *TestSuiteIAM) TestServiceAccountPrivilegeEscalationBug(c *check) {
}
}

func (s *TestSuiteIAM) TestServiceAccountPrivilegeEscalationBug2_2025_10_15(c *check, forRoot bool) {
ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout)
defer cancel()

for i := range 3 {
err := s.client.MakeBucket(ctx, fmt.Sprintf("bucket%d", i+1), minio.MakeBucketOptions{})
if err != nil {
c.Fatalf("bucket create error: %v", err)
}
defer func(i int) {
_ = s.client.RemoveBucket(ctx, fmt.Sprintf("bucket%d", i+1))
}(i)
}

allow2BucketsPolicyBytes := []byte(`{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ListBucket1AndBucket2",
"Effect": "Allow",
"Action": ["s3:ListBucket"],
"Resource": ["arn:aws:s3:::bucket1", "arn:aws:s3:::bucket2"]
},
{
"Sid": "ReadWriteBucket1AndBucket2Objects",
"Effect": "Allow",
"Action": [
"s3:DeleteObject",
"s3:DeleteObjectVersion",
"s3:GetObject",
"s3:GetObjectVersion",
"s3:PutObject"
],
"Resource": ["arn:aws:s3:::bucket1/*", "arn:aws:s3:::bucket2/*"]
}
]
}`)

if forRoot {
// Create a service account for the root user.
_, err := s.adm.AddServiceAccount(ctx, madmin.AddServiceAccountReq{
Policy: allow2BucketsPolicyBytes,
AccessKey: "restricted",
SecretKey: "restricted123",
})
if err != nil {
c.Fatalf("could not create service account")
}
defer func() {
_ = s.adm.DeleteServiceAccount(ctx, "restricted")
}()
} else {
// Create a regular user and attach consoleAdmin policy
err := s.adm.AddUser(ctx, "foobar", "foobar123")
if err != nil {
c.Fatalf("could not create user")
}

_, err = s.adm.AttachPolicy(ctx, madmin.PolicyAssociationReq{
Policies: []string{"consoleAdmin"},
User: "foobar",
})
if err != nil {
c.Fatalf("could not attach policy")
}

// Create a service account for the regular user.
_, err = s.adm.AddServiceAccount(ctx, madmin.AddServiceAccountReq{
Policy: allow2BucketsPolicyBytes,
TargetUser: "foobar",
AccessKey: "restricted",
SecretKey: "restricted123",
})
if err != nil {
c.Fatalf("could not create service account: %v", err)
}
defer func() {
_ = s.adm.DeleteServiceAccount(ctx, "restricted")
_ = s.adm.RemoveUser(ctx, "foobar")
}()
}
restrictedClient := s.getUserClient(c, "restricted", "restricted123", "")

buckets, err := restrictedClient.ListBuckets(ctx)
if err != nil {
c.Fatalf("err fetching buckets %s", err)
}
if len(buckets) != 2 || buckets[0].Name != "bucket1" || buckets[1].Name != "bucket2" {
c.Fatalf("restricted service account should only have access to bucket1 and bucket2")
}

// Try to escalate privileges
restrictedAdmClient := s.getAdminClient(c, "restricted", "restricted123", "")
_, err = restrictedAdmClient.AddServiceAccount(ctx, madmin.AddServiceAccountReq{
AccessKey: "newroot",
SecretKey: "newroot123",
})
if err == nil {
c.Fatalf("restricted service account was able to create service account bypassing sub-policy!")
}
}

func (s *TestSuiteIAM) SetUpAccMgmtPlugin(c *check) {
ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout)
defer cancel()
Expand Down
33 changes: 18 additions & 15 deletions cmd/iam.go
Original file line number Diff line number Diff line change
Expand Up @@ -2400,21 +2400,8 @@ func isAllowedBySessionPolicyForServiceAccount(args policy.Args) (hasSessionPoli
// policy, regardless of whether the number of statements is 0, this
// includes `null`, `{}` and `{"Statement": null}`. In fact, MinIO Console
// sends `null` when no policy is set and the intended behavior is that the
// service account should inherit parent policy.
//
// However, for a policy like `{"Statement":[]}`, the intention is to not
// provide any permissions via the session policy - i.e. the service account
// can do nothing (such a JSON could be generated by an external application
// as the policy for the service account). Inheriting the parent policy in
// such a case, is a security issue. Ideally, we should not allow such
// behavior, but for compatibility with the Console, we currently allow it.
//
// TODO:
//
// 1. fix console behavior and allow this inheritance for service accounts
// created before a certain (TBD) future date.
//
// 2. do not allow empty statement policies for service accounts.
// service account should inherit parent policy. So when policy is empty in
// all fields we return hasSessionPolicy=false.
if subPolicy.Version == "" && subPolicy.Statements == nil && subPolicy.ID == "" {
hasSessionPolicy = false
return hasSessionPolicy, isAllowed
Expand All @@ -2423,8 +2410,16 @@ func isAllowedBySessionPolicyForServiceAccount(args policy.Args) (hasSessionPoli
// As the session policy exists, even if the parent is the root account, it
// must be restricted by it. So, we set `.IsOwner` to false here
// unconditionally.
//
// We also set `DenyOnly` arg to false here - this is an IMPORTANT corner
// case: DenyOnly is used only for allowing an account to do actions related
// to its own account (like create service accounts for itself, among
// others). However when a session policy is present, we need to validate
// that the action is actually allowed, rather than checking if the action
// is only disallowed.
sessionPolicyArgs := args
sessionPolicyArgs.IsOwner = false
sessionPolicyArgs.DenyOnly = false

// Sub policy is set and valid.
return hasSessionPolicy, subPolicy.IsAllowed(sessionPolicyArgs)
Expand Down Expand Up @@ -2465,8 +2460,16 @@ func isAllowedBySessionPolicy(args policy.Args) (hasSessionPolicy bool, isAllowe
// As the session policy exists, even if the parent is the root account, it
// must be restricted by it. So, we set `.IsOwner` to false here
// unconditionally.
//
// We also set `DenyOnly` arg to false here - this is an IMPORTANT corner
// case: DenyOnly is used only for allowing an account to do actions related
// to its own account (like create service accounts for itself, among
// others). However when a session policy is present, we need to validate
// that the action is actually allowed, rather than checking if the action
// is only disallowed.
sessionPolicyArgs := args
sessionPolicyArgs.IsOwner = false
sessionPolicyArgs.DenyOnly = false

// Sub policy is set and valid.
return hasSessionPolicy, subPolicy.IsAllowed(sessionPolicyArgs)
Expand Down
106 changes: 106 additions & 0 deletions cmd/sts-handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ func runAllIAMSTSTests(suite *TestSuiteIAM, c *check) {
// The STS for root test needs to be the first one after setup.
suite.TestSTSForRoot(c)
suite.TestSTS(c)
suite.TestSTSPrivilegeEscalationBug2_2025_10_15(c, true)
suite.TestSTSPrivilegeEscalationBug2_2025_10_15(c, false)
suite.TestSTSWithDenyDeleteVersion(c)
suite.TestSTSWithTags(c)
suite.TestSTSServiceAccountsWithUsername(c)
Expand Down Expand Up @@ -276,6 +278,110 @@ func (s *TestSuiteIAM) TestSTSWithDenyDeleteVersion(c *check) {
c.mustNotDelete(ctx, minioClient, bucket, versions[0])
}

func (s *TestSuiteIAM) TestSTSPrivilegeEscalationBug2_2025_10_15(c *check, forRoot bool) {
ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout)
defer cancel()

for i := range 3 {
err := s.client.MakeBucket(ctx, fmt.Sprintf("bucket%d", i+1), minio.MakeBucketOptions{})
if err != nil {
c.Fatalf("bucket create error: %v", err)
}
defer func(i int) {
_ = s.client.RemoveBucket(ctx, fmt.Sprintf("bucket%d", i+1))
}(i)
}

allow2BucketsPolicyBytes := []byte(`{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ListBucket1AndBucket2",
"Effect": "Allow",
"Action": ["s3:ListBucket"],
"Resource": ["arn:aws:s3:::bucket1", "arn:aws:s3:::bucket2"]
},
{
"Sid": "ReadWriteBucket1AndBucket2Objects",
"Effect": "Allow",
"Action": [
"s3:DeleteObject",
"s3:DeleteObjectVersion",
"s3:GetObject",
"s3:GetObjectVersion",
"s3:PutObject"
],
"Resource": ["arn:aws:s3:::bucket1/*", "arn:aws:s3:::bucket2/*"]
}
]
}`)

var value cr.Value
var err error
if forRoot {
assumeRole := cr.STSAssumeRole{
Client: s.TestSuiteCommon.client,
STSEndpoint: s.endPoint,
Options: cr.STSAssumeRoleOptions{
AccessKey: globalActiveCred.AccessKey,
SecretKey: globalActiveCred.SecretKey,
Policy: string(allow2BucketsPolicyBytes),
},
}
value, err = assumeRole.Retrieve()
if err != nil {
c.Fatalf("err calling assumeRole: %v", err)
}
} else {
// Create a regular user and attach consoleAdmin policy
err := s.adm.AddUser(ctx, "foobar", "foobar123")
if err != nil {
c.Fatalf("could not create user")
}

_, err = s.adm.AttachPolicy(ctx, madmin.PolicyAssociationReq{
Policies: []string{"consoleAdmin"},
User: "foobar",
})
if err != nil {
c.Fatalf("could not attach policy")
}

assumeRole := cr.STSAssumeRole{
Client: s.TestSuiteCommon.client,
STSEndpoint: s.endPoint,
Options: cr.STSAssumeRoleOptions{
AccessKey: "foobar",
SecretKey: "foobar123",
Policy: string(allow2BucketsPolicyBytes),
},
}
value, err = assumeRole.Retrieve()
if err != nil {
c.Fatalf("err calling assumeRole: %v", err)
}
}
restrictedClient := s.getUserClient(c, value.AccessKeyID, value.SecretAccessKey, value.SessionToken)

buckets, err := restrictedClient.ListBuckets(ctx)
if err != nil {
c.Fatalf("err fetching buckets %s", err)
}
if len(buckets) != 2 || buckets[0].Name != "bucket1" || buckets[1].Name != "bucket2" {
c.Fatalf("restricted STS account should only have access to bucket1 and bucket2")
}

// Try to escalate privileges
restrictedAdmClient := s.getAdminClient(c, value.AccessKeyID, value.SecretAccessKey, value.SessionToken)
_, err = restrictedAdmClient.AddServiceAccount(ctx, madmin.AddServiceAccountReq{
AccessKey: "newroot",
SecretKey: "newroot123",
})
if err == nil {
c.Fatalf("restricted STS account was able to create service account bypassing sub-policy!")
}
}

func (s *TestSuiteIAM) TestSTSWithTags(c *check) {
ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout)
defer cancel()
Expand Down
Loading
Loading
0