Skip to content

Commit bbcffa9

Browse files
[SREP-1313] feat : Update isolation workflow to enforce policy Arn from backplane-api assume-role-sequence endpoint (#748)
1 parent dbb0d92 commit bbcffa9

File tree

4 files changed

+836
-8
lines changed

4 files changed

+836
-8
lines changed

cmd/ocm-backplane/cloud/common.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"github.com/aws/aws-sdk-go-v2/aws/arn"
1919
"github.com/aws/aws-sdk-go-v2/credentials"
2020
"github.com/aws/aws-sdk-go-v2/service/sts"
21+
"github.com/aws/aws-sdk-go-v2/service/sts/types"
2122
ocmsdk "github.com/openshift-online/ocm-sdk-go"
2223
cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1"
2324
BackplaneApi "github.com/openshift/backplane-api/pkg/client"
@@ -211,6 +212,7 @@ func (cfg *QueryConfig) getCloudCredentialsFromBackplaneAPI(ocmToken string) (bp
211212
type assumeChainResponse struct {
212213
AssumptionSequence []namedRoleArn `json:"assumptionSequence"`
213214
CustomerRoleSessionName string `json:"customerRoleSessionName"`
215+
SessionPolicyArn string `json:"sessionPolicyArn"` // SessionPolicyArn is the ARN of the session policy
214216
}
215217

216218
type namedRoleArn struct {
@@ -319,6 +321,24 @@ func (cfg *QueryConfig) getIsolatedCredentials(ocmToken string) (aws.Credentials
319321
} else {
320322
roleArnSession.RoleSessionName = email
321323
}
324+
// Default to no policy ARNs
325+
roleArnSession.PolicyARNs = []types.PolicyDescriptorType{}
326+
if namedRoleArnEntry.Name == CustomerRoleArnName {
327+
roleArnSession.IsCustomerRole = true
328+
// Add the session policy ARN for selected roles
329+
if roleChainResponse.SessionPolicyArn != "" {
330+
logger.Debugf("Adding session policy ARN for role %s: %s", namedRoleArnEntry.Name, roleChainResponse.SessionPolicyArn)
331+
roleArnSession.PolicyARNs = []types.PolicyDescriptorType{
332+
{
333+
Arn: aws.String(roleChainResponse.SessionPolicyArn),
334+
},
335+
}
336+
}
337+
} else {
338+
roleArnSession.IsCustomerRole = false
339+
}
340+
roleArnSession.Name = namedRoleArnEntry.Name
341+
322342
assumeRoleArnSessionSequence = append(assumeRoleArnSessionSequence, roleArnSession)
323343
}
324344

@@ -471,7 +491,7 @@ func isIsolatedBackplaneAccess(cluster *cmv1.Cluster, ocmConnection *ocmsdk.Conn
471491
if strings.HasSuffix(baseDomain, "devshiftusgov.com") || strings.HasSuffix(baseDomain, "openshiftusgov.com") {
472492
return false, nil
473493
}
474-
494+
475495
if cluster.Hypershift().Enabled() {
476496
return true, nil
477497
}

cmd/ocm-backplane/cloud/common_test.go

Lines changed: 312 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"net"
88
"net/http"
99
"net/http/httptest"
10+
"strings"
1011
"testing"
1112

1213
"github.com/openshift/backplane-cli/pkg/awsutil"
@@ -15,6 +16,7 @@ import (
1516
"github.com/aws/aws-sdk-go-v2/credentials"
1617
"github.com/aws/aws-sdk-go-v2/credentials/stscreds"
1718
"github.com/aws/aws-sdk-go-v2/service/sts"
19+
"github.com/aws/aws-sdk-go-v2/service/sts/types"
1820
"github.com/golang/mock/gomock"
1921
. "github.com/onsi/ginkgo/v2"
2022
. "github.com/onsi/gomega"
@@ -523,3 +525,313 @@ var _ = Describe("isIsolatedBackplaneAccess", func() {
523525
})
524526
})
525527
})
528+
529+
var _ = Describe("PolicyARNs Integration", func() {
530+
var (
531+
testSessionPolicyArn string
532+
)
533+
534+
BeforeEach(func() {
535+
testSessionPolicyArn = "arn:aws:iam::123456789012:policy/TestSessionPolicy"
536+
})
537+
538+
// Helper function to simulate the getIsolatedCredentials logic
539+
simulateGetIsolatedCredentialsLogic := func(roleChainResponse assumeChainResponse) []awsutil.RoleArnSession {
540+
assumeRoleArnSessionSequence := make([]awsutil.RoleArnSession, 0, len(roleChainResponse.AssumptionSequence))
541+
for _, namedRoleArnEntry := range roleChainResponse.AssumptionSequence {
542+
roleArnSession := awsutil.RoleArnSession{RoleArn: namedRoleArnEntry.Arn}
543+
if namedRoleArnEntry.Name == CustomerRoleArnName || namedRoleArnEntry.Name == OrgRoleArnName {
544+
roleArnSession.RoleSessionName = roleChainResponse.CustomerRoleSessionName
545+
} else {
546+
roleArnSession.RoleSessionName = "test@example.com"
547+
}
548+
549+
// Default to no policy ARNs
550+
roleArnSession.PolicyARNs = []types.PolicyDescriptorType{}
551+
if namedRoleArnEntry.Name == CustomerRoleArnName {
552+
roleArnSession.IsCustomerRole = true
553+
// Add the session policy ARN for selected roles
554+
if roleChainResponse.SessionPolicyArn != "" {
555+
roleArnSession.PolicyARNs = []types.PolicyDescriptorType{
556+
{
557+
Arn: aws.String(roleChainResponse.SessionPolicyArn),
558+
},
559+
}
560+
}
561+
} else {
562+
roleArnSession.IsCustomerRole = false
563+
}
564+
roleArnSession.Name = namedRoleArnEntry.Name
565+
566+
assumeRoleArnSessionSequence = append(assumeRoleArnSessionSequence, roleArnSession)
567+
}
568+
return assumeRoleArnSessionSequence
569+
}
570+
571+
// Generated by Cursor
572+
Context("when creating RoleArnSession with SessionPolicyArn", func() {
573+
It("should set PolicyARNs for customer roles", func() {
574+
roleChainResponse := assumeChainResponse{
575+
AssumptionSequence: []namedRoleArn{
576+
{
577+
Name: CustomerRoleArnName,
578+
Arn: "arn:aws:iam::123456789012:role/customer-role",
579+
},
580+
},
581+
CustomerRoleSessionName: "customer-session",
582+
SessionPolicyArn: testSessionPolicyArn,
583+
}
584+
585+
assumeRoleArnSessionSequence := simulateGetIsolatedCredentialsLogic(roleChainResponse)
586+
587+
Expect(len(assumeRoleArnSessionSequence)).To(Equal(1))
588+
589+
customerRole := assumeRoleArnSessionSequence[0]
590+
Expect(customerRole.IsCustomerRole).To(BeTrue())
591+
Expect(customerRole.Name).To(Equal(CustomerRoleArnName))
592+
Expect(len(customerRole.PolicyARNs)).To(Equal(1))
593+
Expect(*customerRole.PolicyARNs[0].Arn).To(Equal(testSessionPolicyArn))
594+
})
595+
596+
It("should not set PolicyARNs for non-customer roles", func() {
597+
roleChainResponse := assumeChainResponse{
598+
AssumptionSequence: []namedRoleArn{
599+
{
600+
Name: "Support-Role-Arn",
601+
Arn: "arn:aws:iam::123456789012:role/support-role",
602+
},
603+
},
604+
CustomerRoleSessionName: "customer-session",
605+
SessionPolicyArn: testSessionPolicyArn,
606+
}
607+
608+
assumeRoleArnSessionSequence := simulateGetIsolatedCredentialsLogic(roleChainResponse)
609+
610+
Expect(len(assumeRoleArnSessionSequence)).To(Equal(1))
611+
612+
supportRole := assumeRoleArnSessionSequence[0]
613+
Expect(supportRole.IsCustomerRole).To(BeFalse())
614+
Expect(supportRole.Name).To(Equal("Support-Role-Arn"))
615+
Expect(len(supportRole.PolicyARNs)).To(Equal(0))
616+
})
617+
618+
// Generated by Cursor
619+
It("should handle empty SessionPolicyArn for customer roles", func() {
620+
roleChainResponse := assumeChainResponse{
621+
AssumptionSequence: []namedRoleArn{
622+
{
623+
Name: CustomerRoleArnName,
624+
Arn: "arn:aws:iam::123456789012:role/customer-role",
625+
},
626+
},
627+
CustomerRoleSessionName: "customer-session",
628+
SessionPolicyArn: "", // Empty session policy ARN
629+
}
630+
631+
assumeRoleArnSessionSequence := simulateGetIsolatedCredentialsLogic(roleChainResponse)
632+
633+
Expect(len(assumeRoleArnSessionSequence)).To(Equal(1))
634+
635+
customerRole := assumeRoleArnSessionSequence[0]
636+
Expect(customerRole.IsCustomerRole).To(BeTrue())
637+
Expect(customerRole.Name).To(Equal(CustomerRoleArnName))
638+
Expect(len(customerRole.PolicyARNs)).To(Equal(0))
639+
})
640+
})
641+
642+
Context("error scenarios with PolicyARNs", func() {
643+
It("should handle invalid SessionPolicyArn gracefully", func() {
644+
roleChainResponse := assumeChainResponse{
645+
AssumptionSequence: []namedRoleArn{
646+
{
647+
Name: CustomerRoleArnName,
648+
Arn: "arn:aws:iam::123456789012:role/customer-role",
649+
},
650+
},
651+
CustomerRoleSessionName: "customer-session",
652+
SessionPolicyArn: "invalid-arn-format", // Invalid ARN format
653+
}
654+
655+
assumeRoleArnSessionSequence := simulateGetIsolatedCredentialsLogic(roleChainResponse)
656+
657+
Expect(len(assumeRoleArnSessionSequence)).To(Equal(1))
658+
659+
customerRole := assumeRoleArnSessionSequence[0]
660+
Expect(customerRole.IsCustomerRole).To(BeTrue())
661+
Expect(customerRole.Name).To(Equal(CustomerRoleArnName))
662+
Expect(len(customerRole.PolicyARNs)).To(Equal(1))
663+
// The invalid ARN is still passed through - validation happens at AWS level
664+
Expect(*customerRole.PolicyARNs[0].Arn).To(Equal("invalid-arn-format"))
665+
})
666+
667+
It("should handle missing AssumptionSequence", func() {
668+
roleChainResponse := assumeChainResponse{
669+
AssumptionSequence: []namedRoleArn{}, // Empty sequence
670+
CustomerRoleSessionName: "customer-session",
671+
SessionPolicyArn: testSessionPolicyArn,
672+
}
673+
674+
assumeRoleArnSessionSequence := simulateGetIsolatedCredentialsLogic(roleChainResponse)
675+
676+
// Should result in empty sequence
677+
Expect(len(assumeRoleArnSessionSequence)).To(Equal(0))
678+
})
679+
680+
It("should handle malformed role ARNs in AssumptionSequence", func() {
681+
roleChainResponse := assumeChainResponse{
682+
AssumptionSequence: []namedRoleArn{
683+
{
684+
Name: CustomerRoleArnName,
685+
Arn: "malformed-role-arn", // Invalid role ARN format
686+
},
687+
},
688+
CustomerRoleSessionName: "customer-session",
689+
SessionPolicyArn: testSessionPolicyArn,
690+
}
691+
692+
assumeRoleArnSessionSequence := simulateGetIsolatedCredentialsLogic(roleChainResponse)
693+
694+
Expect(len(assumeRoleArnSessionSequence)).To(Equal(1))
695+
696+
customerRole := assumeRoleArnSessionSequence[0]
697+
Expect(customerRole.IsCustomerRole).To(BeTrue())
698+
Expect(customerRole.RoleArn).To(Equal("malformed-role-arn")) // Malformed ARN is passed through
699+
Expect(len(customerRole.PolicyARNs)).To(Equal(1))
700+
Expect(*customerRole.PolicyARNs[0].Arn).To(Equal(testSessionPolicyArn))
701+
})
702+
703+
It("should handle extremely long SessionPolicyArn", func() {
704+
// Create a very long policy ARN that might cause issues
705+
longPolicyArn := "arn:aws:iam::123456789012:policy/" + strings.Repeat("a", 500)
706+
707+
roleChainResponse := assumeChainResponse{
708+
AssumptionSequence: []namedRoleArn{
709+
{
710+
Name: CustomerRoleArnName,
711+
Arn: "arn:aws:iam::123456789012:role/customer-role",
712+
},
713+
},
714+
CustomerRoleSessionName: "customer-session",
715+
SessionPolicyArn: longPolicyArn,
716+
}
717+
718+
assumeRoleArnSessionSequence := simulateGetIsolatedCredentialsLogic(roleChainResponse)
719+
720+
Expect(len(assumeRoleArnSessionSequence)).To(Equal(1))
721+
722+
customerRole := assumeRoleArnSessionSequence[0]
723+
Expect(customerRole.IsCustomerRole).To(BeTrue())
724+
Expect(len(customerRole.PolicyARNs)).To(Equal(1))
725+
Expect(*customerRole.PolicyARNs[0].Arn).To(Equal(longPolicyArn))
726+
// Verify the ARN length
727+
Expect(len(*customerRole.PolicyARNs[0].Arn)).To(BeNumerically(">", 500))
728+
})
729+
730+
It("should handle special characters in SessionPolicyArn", func() {
731+
// Policy ARN with special characters (though this would be invalid in real AWS)
732+
specialCharsArn := "arn:aws:iam::123456789012:policy/test-policy-with-special-chars!@#$%"
733+
734+
roleChainResponse := assumeChainResponse{
735+
AssumptionSequence: []namedRoleArn{
736+
{
737+
Name: CustomerRoleArnName,
738+
Arn: "arn:aws:iam::123456789012:role/customer-role",
739+
},
740+
},
741+
CustomerRoleSessionName: "customer-session",
742+
SessionPolicyArn: specialCharsArn,
743+
}
744+
745+
assumeRoleArnSessionSequence := simulateGetIsolatedCredentialsLogic(roleChainResponse)
746+
747+
Expect(len(assumeRoleArnSessionSequence)).To(Equal(1))
748+
749+
customerRole := assumeRoleArnSessionSequence[0]
750+
Expect(customerRole.IsCustomerRole).To(BeTrue())
751+
Expect(len(customerRole.PolicyARNs)).To(Equal(1))
752+
Expect(*customerRole.PolicyARNs[0].Arn).To(Equal(specialCharsArn))
753+
})
754+
755+
It("should verify debug logging when SessionPolicyArn is non-empty", func() {
756+
roleChainResponse := assumeChainResponse{
757+
AssumptionSequence: []namedRoleArn{
758+
{
759+
Name: CustomerRoleArnName,
760+
Arn: "arn:aws:iam::123456789012:role/customer-role",
761+
},
762+
},
763+
CustomerRoleSessionName: "customer-session",
764+
SessionPolicyArn: testSessionPolicyArn, // Non-empty SessionPolicyArn
765+
}
766+
767+
assumeRoleArnSessionSequence := simulateGetIsolatedCredentialsLogic(roleChainResponse)
768+
769+
// Verify the non-empty SessionPolicyArn scenario
770+
Expect(len(assumeRoleArnSessionSequence)).To(Equal(1))
771+
772+
customerRole := assumeRoleArnSessionSequence[0]
773+
774+
// Verify the customer role identification
775+
Expect(customerRole.IsCustomerRole).To(BeTrue())
776+
Expect(customerRole.Name).To(Equal(CustomerRoleArnName))
777+
Expect(customerRole.RoleSessionName).To(Equal("customer-session"))
778+
779+
// Verify that SessionPolicyArn is non-empty
780+
Expect(roleChainResponse.SessionPolicyArn).ToNot(BeEmpty())
781+
782+
// Verify PolicyARNs array is populated correctly
783+
Expect(len(customerRole.PolicyARNs)).To(Equal(1))
784+
Expect(customerRole.PolicyARNs[0].Arn).ToNot(BeNil())
785+
Expect(*customerRole.PolicyARNs[0].Arn).To(Equal(testSessionPolicyArn))
786+
787+
// Verify the exact SessionPolicyArn value matches
788+
Expect(*customerRole.PolicyARNs[0].Arn).To(Equal(roleChainResponse.SessionPolicyArn))
789+
})
790+
791+
It("should handle multiple customer roles with same SessionPolicyArn", func() {
792+
// Test scenario with multiple customer roles getting the same session policy
793+
roleChainResponse := assumeChainResponse{
794+
AssumptionSequence: []namedRoleArn{
795+
{
796+
Name: CustomerRoleArnName,
797+
Arn: "arn:aws:iam::123456789012:role/customer-role-1",
798+
},
799+
{
800+
Name: "Support-Role-Arn",
801+
Arn: "arn:aws:iam::123456789012:role/support-role",
802+
},
803+
{
804+
Name: CustomerRoleArnName, // Another customer role
805+
Arn: "arn:aws:iam::123456789012:role/customer-role-2",
806+
},
807+
},
808+
CustomerRoleSessionName: "customer-session",
809+
SessionPolicyArn: testSessionPolicyArn,
810+
}
811+
812+
assumeRoleArnSessionSequence := simulateGetIsolatedCredentialsLogic(roleChainResponse)
813+
814+
Expect(len(assumeRoleArnSessionSequence)).To(Equal(3))
815+
816+
// Verify first customer role
817+
customerRole1 := assumeRoleArnSessionSequence[0]
818+
Expect(customerRole1.IsCustomerRole).To(BeTrue())
819+
Expect(customerRole1.Name).To(Equal(CustomerRoleArnName))
820+
Expect(len(customerRole1.PolicyARNs)).To(Equal(1))
821+
Expect(*customerRole1.PolicyARNs[0].Arn).To(Equal(testSessionPolicyArn))
822+
823+
// Verify support role (non-customer)
824+
supportRole := assumeRoleArnSessionSequence[1]
825+
Expect(supportRole.IsCustomerRole).To(BeFalse())
826+
Expect(supportRole.Name).To(Equal("Support-Role-Arn"))
827+
Expect(len(supportRole.PolicyARNs)).To(Equal(0))
828+
829+
// Verify second customer role
830+
customerRole2 := assumeRoleArnSessionSequence[2]
831+
Expect(customerRole2.IsCustomerRole).To(BeTrue())
832+
Expect(customerRole2.Name).To(Equal(CustomerRoleArnName))
833+
Expect(len(customerRole2.PolicyARNs)).To(Equal(1))
834+
Expect(*customerRole2.PolicyARNs[0].Arn).To(Equal(testSessionPolicyArn))
835+
})
836+
})
837+
})

0 commit comments

Comments
 (0)