mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-20 13:04:25 +02:00
iam: scope CreateServiceAccount's ParentUser by policy resource (#11736)
* iam: scope CreateServiceAccount's ParentUser by policy resource AuthorizeIamAction evaluated the iam:CreateServiceAccount grant against a hardcoded arn:aws:iam:::* resource, so the policy's Resource never had a say; #11218 then bound the target to the caller outright. Evaluate self-target actions against the target identity's ARN instead: a wildcard resource keeps the deliberate any-target grant, a scoped resource confines issuance to the named parent identity, anything else is denied. buildResourceARN gains the ARN pass-through buildS3ResourceArn already has, so iam: ARNs reach the policy engine intact on the attached-policies route. Closes #11735 * ci: retrigger after unrelated weed/storage metrics flake * iam: fold IAM-ARN matching into awsIAMMatch, drop debug test - remove zz_debug_test.go (local debugging leftover) - fold awsIAMMatchResource into awsIAMMatch: the arn:aws:iam:: value prefix selects case-sensitive matching, so no separate name is needed - trim comments --------- Co-authored-by: batonogov <batonogov@users.noreply.github.com> Co-authored-by: Chris Lu <chris.lu@gmail.com>
This commit is contained in:
5 files changed
+140
-50
No files matched your search
@@ -1240,6 +1240,18 @@ func awsIAMMatch(pattern, value string, evalCtx *EvaluationContext) bool {
|
|||||||
return true // Universal wildcard
|
return true // Universal wildcard
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IAM identity ARNs match case-sensitively: identity lookup is
|
||||||
|
// case-sensitive, so a case-variant names a different identity.
|
||||||
|
if strings.HasPrefix(value, "arn:aws:iam::") {
|
||||||
|
if expandedPattern == value {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if strings.Contains(expandedPattern, "*") || strings.Contains(expandedPattern, "?") {
|
||||||
|
return wildcard.MatchesWildcard(expandedPattern, value)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Step 3: Case-insensitive exact match
|
// Step 3: Case-insensitive exact match
|
||||||
if strings.EqualFold(expandedPattern, value) {
|
if strings.EqualFold(expandedPattern, value) {
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -895,6 +895,11 @@ func isPublicReadGrants(grants []*s3.Grant) bool {
|
|||||||
// buildResourceARN builds a resource ARN from bucket and object
|
// buildResourceARN builds a resource ARN from bucket and object
|
||||||
// Used by the policy engine wrapper
|
// Used by the policy engine wrapper
|
||||||
func buildResourceARN(bucket, object string) string {
|
func buildResourceARN(bucket, object string) string {
|
||||||
|
// An already-ARN resource (IAM management actions authorize against
|
||||||
|
// arn:aws:iam::...) passes through; no bucket name can contain ':'.
|
||||||
|
if strings.HasPrefix(bucket, "arn:") {
|
||||||
|
return bucket
|
||||||
|
}
|
||||||
if object == "" || object == "/" {
|
if object == "" || object == "/" {
|
||||||
return fmt.Sprintf("arn:aws:s3:::%s", bucket)
|
return fmt.Sprintf("arn:aws:s3:::%s", bucket)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2528,9 +2528,10 @@ func iamRequiresAdminForOthers(action string) bool {
|
|||||||
return iamSelfServiceActions[action]
|
return iamSelfServiceActions[action]
|
||||||
}
|
}
|
||||||
|
|
||||||
// iamSelfTargetActions require an explicit iam:<Action> grant, but a non-admin
|
// iamSelfTargetActions are authorized against the target identity's ARN so a
|
||||||
// grant holder may only target their own identity. The target parameter is
|
// policy can scope the grant (arn:aws:iam:::user/team-parent). The target
|
||||||
// action-specific (UserName for most, ParentUser for CreateServiceAccount).
|
// parameter is action-specific (UserName for most, ParentUser for
|
||||||
|
// CreateServiceAccount).
|
||||||
var iamSelfTargetActions = map[string]bool{
|
var iamSelfTargetActions = map[string]bool{
|
||||||
"CreateServiceAccount": true,
|
"CreateServiceAccount": true,
|
||||||
}
|
}
|
||||||
@@ -2573,13 +2574,14 @@ func (iam *IdentityAccessManagement) AuthorizeIamAction(r *http.Request, identit
|
|||||||
if identity.isAdmin() {
|
if identity.isAdmin() {
|
||||||
return s3err.ErrNone
|
return s3err.ErrNone
|
||||||
}
|
}
|
||||||
if errCode := iam.VerifyActionPermission(r, identity, Action("iam:"+action), "arn:aws:iam:::*", ""); errCode != s3err.ErrNone {
|
// Self-target actions authorize against the target identity's ARN. The
|
||||||
return errCode
|
// account segment stays empty so existing arn:aws:iam:::* wildcard
|
||||||
|
// grants keep matching.
|
||||||
|
resource := "arn:aws:iam:::*"
|
||||||
|
if iamRequiresSelfTarget(action) && targetUserName != "" {
|
||||||
|
resource = fmt.Sprintf("arn:aws:iam:::user/%s", targetUserName)
|
||||||
}
|
}
|
||||||
if iamRequiresSelfTarget(action) && targetUserName != "" && targetUserName != identity.Name {
|
return iam.VerifyActionPermission(r, identity, Action("iam:"+action), resource, "")
|
||||||
return s3err.ErrAccessDenied
|
|
||||||
}
|
|
||||||
return s3err.ErrNone
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthIamManagement authenticates an IAM management request and authorizes the
|
// AuthIamManagement authenticates an IAM management request and authorizes the
|
||||||
|
|||||||
@@ -15,6 +15,11 @@ const (
|
|||||||
dataPlanePolicy = `{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"s3:*","Resource":"*"}]}`
|
dataPlanePolicy = `{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"s3:*","Resource":"*"}]}`
|
||||||
iamAdminPolicy = `{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"iam:*","Resource":"*"}]}`
|
iamAdminPolicy = `{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"iam:*","Resource":"*"}]}`
|
||||||
iamCreateSvcAcctPolicy = `{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["iam:CreateServiceAccount"],"Resource":["*"]}]}`
|
iamCreateSvcAcctPolicy = `{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["iam:CreateServiceAccount"],"Resource":["*"]}]}`
|
||||||
|
// The wildcard ARN shape this authorization always used; it must keep
|
||||||
|
// matching any parent so existing grants survive.
|
||||||
|
iamCreateSvcAcctWildcardArnPolicy = `{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["iam:CreateServiceAccount"],"Resource":["arn:aws:iam:::*"]}]}`
|
||||||
|
// Scoped to one parent identity.
|
||||||
|
iamCreateSvcAcctZonePolicy = `{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["iam:CreateServiceAccount"],"Resource":["arn:aws:iam:::user/team-parent"]}]}`
|
||||||
)
|
)
|
||||||
|
|
||||||
func newIamAuthzTestIam(t *testing.T) *IdentityAccessManagement {
|
func newIamAuthzTestIam(t *testing.T) *IdentityAccessManagement {
|
||||||
@@ -95,22 +100,48 @@ func TestAuthorizeIamActionDeniesAnonymous(t *testing.T) {
|
|||||||
iam.AuthorizeIamAction(iamPostRequest(""), anonymous, "CreateUser", "victim"))
|
iam.AuthorizeIamAction(iamPostRequest(""), anonymous, "CreateUser", "victim"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateServiceAccount takes its target from the ParentUser parameter, not
|
// CreateServiceAccount takes its target from the ParentUser parameter. A
|
||||||
// UserName. A non-admin holding only iam:CreateServiceAccount must not be able
|
// non-admin holding iam:CreateServiceAccount targets the identities its
|
||||||
// to mint a service account for another identity (privilege escalation to that
|
// policy resource covers: a wildcard resource allows any target, a scoped
|
||||||
// identity's permissions).
|
// resource confines the grant to the named parent identity.
|
||||||
func TestAuthorizeIamActionCreateServiceAccountBindsTargetToCaller(t *testing.T) {
|
func TestAuthorizeIamActionCreateServiceAccountScopedByPolicyResource(t *testing.T) {
|
||||||
iam := newIamAuthzTestIam(t)
|
iam := newIamAuthzTestIam(t)
|
||||||
require.NoError(t, iam.PutPolicy("CreateSvcAcctPolicy", iamCreateSvcAcctPolicy))
|
require.NoError(t, iam.PutPolicy("CreateSvcAcctPolicy", iamCreateSvcAcctPolicy))
|
||||||
|
require.NoError(t, iam.PutPolicy("CreateSvcAcctWildcardArnPolicy", iamCreateSvcAcctWildcardArnPolicy))
|
||||||
|
require.NoError(t, iam.PutPolicy("CreateSvcAcctZonePolicy", iamCreateSvcAcctZonePolicy))
|
||||||
dev := &Identity{Name: "dev", PolicyNames: []string{"CreateSvcAcctPolicy"}}
|
dev := &Identity{Name: "dev", PolicyNames: []string{"CreateSvcAcctPolicy"}}
|
||||||
|
wildcardArn := &Identity{Name: "wildcard-arn", PolicyNames: []string{"CreateSvcAcctWildcardArnPolicy"}}
|
||||||
|
keymaker := &Identity{Name: "keymaker", PolicyNames: []string{"CreateSvcAcctZonePolicy"}}
|
||||||
admin := &Identity{Name: "admin", Actions: []Action{s3_constants.ACTION_ADMIN}}
|
admin := &Identity{Name: "admin", Actions: []Action{s3_constants.ACTION_ADMIN}}
|
||||||
|
|
||||||
|
// Wildcard resource: any target, including the caller itself.
|
||||||
assert.Equal(t, s3err.ErrNone,
|
assert.Equal(t, s3err.ErrNone,
|
||||||
iam.AuthorizeIamAction(iamPostRequest(""), dev, "CreateServiceAccount", "dev"))
|
iam.AuthorizeIamAction(iamPostRequest(""), dev, "CreateServiceAccount", "dev"))
|
||||||
assert.Equal(t, s3err.ErrNone,
|
assert.Equal(t, s3err.ErrNone,
|
||||||
iam.AuthorizeIamAction(iamPostRequest(""), dev, "CreateServiceAccount", ""))
|
iam.AuthorizeIamAction(iamPostRequest(""), dev, "CreateServiceAccount", ""))
|
||||||
assert.Equal(t, s3err.ErrAccessDenied,
|
assert.Equal(t, s3err.ErrNone,
|
||||||
iam.AuthorizeIamAction(iamPostRequest(""), dev, "CreateServiceAccount", "admin"))
|
iam.AuthorizeIamAction(iamPostRequest(""), dev, "CreateServiceAccount", "admin"))
|
||||||
|
|
||||||
|
// Wildcard ARN: any target.
|
||||||
|
assert.Equal(t, s3err.ErrNone,
|
||||||
|
iam.AuthorizeIamAction(iamPostRequest(""), wildcardArn, "CreateServiceAccount", "team-parent"))
|
||||||
|
assert.Equal(t, s3err.ErrNone,
|
||||||
|
iam.AuthorizeIamAction(iamPostRequest(""), wildcardArn, "CreateServiceAccount", "admin"))
|
||||||
|
|
||||||
|
// Scoped resource: the named parent only. IAM resource matching is
|
||||||
|
// case-sensitive, so a case variant is a different identity.
|
||||||
|
assert.Equal(t, s3err.ErrNone,
|
||||||
|
iam.AuthorizeIamAction(iamPostRequest(""), keymaker, "CreateServiceAccount", "team-parent"))
|
||||||
|
assert.Equal(t, s3err.ErrAccessDenied,
|
||||||
|
iam.AuthorizeIamAction(iamPostRequest(""), keymaker, "CreateServiceAccount", "Team-Parent"))
|
||||||
|
assert.Equal(t, s3err.ErrAccessDenied,
|
||||||
|
iam.AuthorizeIamAction(iamPostRequest(""), keymaker, "CreateServiceAccount", "admin"))
|
||||||
|
assert.Equal(t, s3err.ErrAccessDenied,
|
||||||
|
iam.AuthorizeIamAction(iamPostRequest(""), keymaker, "CreateServiceAccount", "keymaker"))
|
||||||
|
assert.Equal(t, s3err.ErrAccessDenied,
|
||||||
|
iam.AuthorizeIamAction(iamPostRequest(""), keymaker, "CreateServiceAccount", ""))
|
||||||
|
|
||||||
|
// Admins remain unrestricted.
|
||||||
assert.Equal(t, s3err.ErrNone,
|
assert.Equal(t, s3err.ErrNone,
|
||||||
iam.AuthorizeIamAction(iamPostRequest(""), admin, "CreateServiceAccount", "victim"))
|
iam.AuthorizeIamAction(iamPostRequest(""), admin, "CreateServiceAccount", "victim"))
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package s3api
|
package s3api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -12,9 +13,14 @@ import (
|
|||||||
v4 "github.com/aws/aws-sdk-go/aws/signer/v4"
|
v4 "github.com/aws/aws-sdk-go/aws/signer/v4"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/seaweedfs/seaweedfs/weed/credential"
|
"github.com/seaweedfs/seaweedfs/weed/credential"
|
||||||
|
"github.com/seaweedfs/seaweedfs/weed/iam/integration"
|
||||||
|
"github.com/seaweedfs/seaweedfs/weed/iam/policy"
|
||||||
|
"github.com/seaweedfs/seaweedfs/weed/iam/sts"
|
||||||
|
"github.com/seaweedfs/seaweedfs/weed/pb/iam_pb"
|
||||||
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
|
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
|
||||||
"github.com/seaweedfs/seaweedfs/weed/util"
|
"github.com/seaweedfs/seaweedfs/weed/util"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
// routingTestAccessKey/routingTestSecretKey are the credentials seeded into
|
// routingTestAccessKey/routingTestSecretKey are the credentials seeded into
|
||||||
@@ -205,50 +211,73 @@ func TestRouting_AuthenticatedIAM(t *testing.T) {
|
|||||||
assert.Contains(t, []int{http.StatusBadRequest, http.StatusForbidden}, rr.Code, "Should route to IAM handler (400/403 due to invalid signature)")
|
assert.Contains(t, []int{http.StatusBadRequest, http.StatusForbidden}, rr.Code, "Should route to IAM handler (400/403 due to invalid signature)")
|
||||||
}
|
}
|
||||||
|
|
||||||
// setupRoutingTestServerWithCreator seeds a non-admin identity (creator) that
|
// setupRoutingTestServerWithCreator seeds two non-admin identities holding
|
||||||
// holds only the iam:CreateServiceAccount action, plus a victim identity, so a
|
// iam:CreateServiceAccount — creator with the coarse (unscoped) action,
|
||||||
// SigV4-signed CreateServiceAccount request can exercise UnifiedPostHandler's
|
// keymaker with a policy scoped to the team-parent identity ARN — plus a
|
||||||
// authorization against the ParentUser target.
|
// victim identity, so SigV4-signed CreateServiceAccount requests can exercise
|
||||||
|
// UnifiedPostHandler's authorization against the ParentUser target.
|
||||||
func setupRoutingTestServerWithCreator(t *testing.T) *S3ApiServer {
|
func setupRoutingTestServerWithCreator(t *testing.T) *S3ApiServer {
|
||||||
s3a := setupRoutingTestServer(t)
|
s3a := setupRoutingTestServer(t)
|
||||||
const creatorAK, creatorSK = "creator-ak", "creator-sk"
|
const creatorAK, creatorSK = "creator-ak", "creator-sk"
|
||||||
creator := &Identity{
|
const keymakerAK, keymakerSK = "keymaker-ak", "keymaker-sk"
|
||||||
Name: "creator",
|
|
||||||
Actions: []Action{Action("iam:CreateServiceAccount")},
|
// Policy-bearing identities route through the IAM integration.
|
||||||
IsStatic: true,
|
mgr := integration.NewIAMManager()
|
||||||
Credentials: []*Credential{{
|
require.NoError(t, mgr.Initialize(&integration.IAMConfig{
|
||||||
AccessKey: creatorAK,
|
STS: &sts.STSConfig{
|
||||||
SecretKey: creatorSK,
|
TokenDuration: sts.FlexibleDuration{Duration: time.Hour},
|
||||||
}},
|
MaxSessionLength: sts.FlexibleDuration{Duration: 12 * time.Hour},
|
||||||
|
Issuer: "test",
|
||||||
|
SigningKey: []byte("test-signing-key-32-characters!!"),
|
||||||
|
AccountId: "111122223333",
|
||||||
|
},
|
||||||
|
Policy: &policy.PolicyEngineConfig{DefaultEffect: "Deny", StoreType: "memory"},
|
||||||
|
Roles: &integration.RoleStoreConfig{StoreType: "memory"},
|
||||||
|
}, func() string { return "localhost:8888" }))
|
||||||
|
s3a.iam.SetIAMIntegration(NewS3IAMIntegration(mgr, ""))
|
||||||
|
|
||||||
|
const zonePolicy = `{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["iam:CreateServiceAccount"],"Resource":["arn:aws:iam:::user/team-parent"]}]}`
|
||||||
|
require.NoError(t, s3a.iam.PutPolicy("CreateSvcAcctZonePolicy", zonePolicy))
|
||||||
|
// Store and memory must agree: SigV4 auth reads memory, the handler
|
||||||
|
// re-reads the store, and a successful creation reloads memory from it.
|
||||||
|
idents := []*iam_pb.Identity{
|
||||||
|
{Name: "creator", Actions: []string{"iam:CreateServiceAccount"},
|
||||||
|
Credentials: []*iam_pb.Credential{{AccessKey: creatorAK, SecretKey: creatorSK}}},
|
||||||
|
{Name: "keymaker", PolicyNames: []string{"CreateSvcAcctZonePolicy"},
|
||||||
|
Credentials: []*iam_pb.Credential{{AccessKey: keymakerAK, SecretKey: keymakerSK}}},
|
||||||
|
{Name: "victim"},
|
||||||
|
{Name: "team-parent"},
|
||||||
|
}
|
||||||
|
require.NoError(t, s3a.credentialManager.SaveConfiguration(context.Background(), &iam_pb.S3ApiConfiguration{
|
||||||
|
Identities: idents,
|
||||||
|
Policies: []*iam_pb.Policy{{Name: "CreateSvcAcctZonePolicy", Content: zonePolicy}},
|
||||||
|
}))
|
||||||
|
for _, ident := range idents {
|
||||||
|
require.NoError(t, s3a.iam.UpsertIdentity(ident))
|
||||||
}
|
}
|
||||||
victim := &Identity{Name: "victim", IsStatic: true}
|
|
||||||
s3a.iam.m.Lock()
|
|
||||||
s3a.iam.identities = append(s3a.iam.identities, creator, victim)
|
|
||||||
s3a.iam.accessKeyIdent[creatorAK] = creator
|
|
||||||
s3a.iam.nameToIdentity["creator"] = creator
|
|
||||||
s3a.iam.nameToIdentity["victim"] = victim
|
|
||||||
s3a.iam.m.Unlock()
|
|
||||||
s3a.cb = NewCircuitBreaker(s3a.option)
|
s3a.cb = NewCircuitBreaker(s3a.option)
|
||||||
return s3a
|
return s3a
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestRouting_CreateServiceAccountBindsParentUser verifies that on the S3-port
|
// TestRouting_CreateServiceAccountParentUserScopedByPolicy verifies that on
|
||||||
// IAM route a non-admin holding iam:CreateServiceAccount cannot mint a service
|
// the S3-port IAM route a non-admin holding iam:CreateServiceAccount targets
|
||||||
// account for another identity (ParentUser=victim), and can for itself.
|
// the identities its grant covers: the coarse action is unscoped (like a
|
||||||
func TestRouting_CreateServiceAccountBindsParentUser(t *testing.T) {
|
// wildcard resource), a policy scoped to one parent identity's ARN confines
|
||||||
|
// the grant to that parent.
|
||||||
|
func TestRouting_CreateServiceAccountParentUserScopedByPolicy(t *testing.T) {
|
||||||
router := mux.NewRouter()
|
router := mux.NewRouter()
|
||||||
s3a := setupRoutingTestServerWithCreator(t)
|
s3a := setupRoutingTestServerWithCreator(t)
|
||||||
s3a.registerRouter(router)
|
s3a.registerRouter(router)
|
||||||
|
|
||||||
signCreator := func(t *testing.T, req *http.Request, body string) {
|
sign := func(t *testing.T, req *http.Request, body, ak, sk string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
creds := credentials.NewStaticCredentials("creator-ak", "creator-sk", "")
|
creds := credentials.NewStaticCredentials(ak, sk, "")
|
||||||
if _, err := v4.NewSigner(creds).Sign(req, strings.NewReader(body), "iam", "us-east-1", time.Now()); err != nil {
|
if _, err := v4.NewSigner(creds).Sign(req, strings.NewReader(body), "iam", "us-east-1", time.Now()); err != nil {
|
||||||
t.Fatalf("sign request: %v", err)
|
t.Fatalf("sign request: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
makeReq := func(parentUser string) *http.Request {
|
makeReq := func(parentUser, ak, sk string) *http.Request {
|
||||||
data := url.Values{}
|
data := url.Values{}
|
||||||
data.Set("Action", "CreateServiceAccount")
|
data.Set("Action", "CreateServiceAccount")
|
||||||
data.Set("Version", "2010-05-08")
|
data.Set("Version", "2010-05-08")
|
||||||
@@ -256,19 +285,30 @@ func TestRouting_CreateServiceAccountBindsParentUser(t *testing.T) {
|
|||||||
body := data.Encode()
|
body := data.Encode()
|
||||||
req, _ := http.NewRequest("POST", "http://localhost/", strings.NewReader(body))
|
req, _ := http.NewRequest("POST", "http://localhost/", strings.NewReader(body))
|
||||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
signCreator(t, req, body)
|
sign(t, req, body, ak, sk)
|
||||||
return req
|
return req
|
||||||
}
|
}
|
||||||
|
|
||||||
// Targeting another identity must be denied at authorization.
|
serve := func(req *http.Request) *httptest.ResponseRecorder {
|
||||||
rr := httptest.NewRecorder()
|
rr := httptest.NewRecorder()
|
||||||
router.ServeHTTP(rr, makeReq("victim"))
|
router.ServeHTTP(rr, req)
|
||||||
assert.Equal(t, http.StatusForbidden, rr.Code, "cross-identity ParentUser must be denied; got body=%s", rr.Body.String())
|
return rr
|
||||||
|
}
|
||||||
|
|
||||||
// Targeting self must pass authorization (handler may still error, but not 403).
|
// Coarse (unscoped) action: any target passes authorization and the
|
||||||
rr2 := httptest.NewRecorder()
|
// service account is actually created.
|
||||||
router.ServeHTTP(rr2, makeReq("creator"))
|
rr := serve(makeReq("victim", "creator-ak", "creator-sk"))
|
||||||
assert.NotEqual(t, http.StatusForbidden, rr2.Code, "self ParentUser must pass authorization; got body=%s", rr2.Body.String())
|
assert.Equal(t, http.StatusOK, rr.Code, "coarse grant is unscoped; got body=%s", rr.Body.String())
|
||||||
|
|
||||||
|
// Scoped policy: the named parent passes (200), any other identity is denied.
|
||||||
|
rr2 := serve(makeReq("team-parent", "keymaker-ak", "keymaker-sk"))
|
||||||
|
assert.Equal(t, http.StatusOK, rr2.Code, "scoped grant must cover its named parent; got body=%s", rr2.Body.String())
|
||||||
|
|
||||||
|
rr3 := serve(makeReq("victim", "keymaker-ak", "keymaker-sk"))
|
||||||
|
assert.Equal(t, http.StatusForbidden, rr3.Code, "target outside the scoped grant must be denied; got body=%s", rr3.Body.String())
|
||||||
|
|
||||||
|
rr4 := serve(makeReq("keymaker", "keymaker-ak", "keymaker-sk"))
|
||||||
|
assert.Equal(t, http.StatusForbidden, rr4.Code, "even self is denied when the scoped grant does not name it; got body=%s", rr4.Body.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestRouting_IAMMatcherLogic verifies the iamMatcher correctly distinguishes auth types
|
// TestRouting_IAMMatcherLogic verifies the iamMatcher correctly distinguishes auth types
|
||||||
|
|||||||
Reference in new issue
Block a user