@@ -5,6 +5,7 @@ package e2e
55
66import (
77 "context"
8+ "encoding/json"
89 "fmt"
910 "strings"
1011 "time"
@@ -29,7 +30,14 @@ import (
2930)
3031
3132var _ = Describe ("enforcing generic metadata namespace rules" , Ordered , Label ("tenant" , "rules" , "enforce" , "metadata" , "generic" ), func () {
32- const ownerName = "e2e-rules-metadata"
33+ const (
34+ ownerName = "e2e-rules-metadata"
35+ secondOwnerName = "e2e-rules-metadata-second"
36+ ownerLabel = "example.corp/label-a"
37+ secondOwnerLabel = "example.corp/label-b"
38+ ownerAnnotation = "example.corp/annotation-a"
39+ secondOwnerAnnotation = "example.corp/annotation-b"
40+ )
3341
3442 var (
3543 tnt * capsulev1beta2.Tenant
@@ -112,6 +120,65 @@ var _ = Describe("enforcing generic metadata namespace rules", Ordered, Label("t
112120 return rule
113121 }
114122
123+ audienceRule := func (
124+ audience rules.Audience ,
125+ rule * rules.NamespaceRuleBodyTenant ,
126+ ) * rules.NamespaceRuleBodyTenant {
127+ rule .Audience = []rules.Audience {audience }
128+
129+ return rule
130+ }
131+
132+ isolatedMetadataRules := func () []* rules.NamespaceRuleBodyTenant {
133+ return []* rules.NamespaceRuleBodyTenant {
134+ audienceRule (
135+ rules.Audience {
136+ Kind : rules .AudienceKindCustom ,
137+ Name : string (rules .CustomAudienceCapsuleUser ),
138+ },
139+ metadataRule (
140+ rules .ActionTypeDeny ,
141+ "v1" ,
142+ []string {"ConfigMap" },
143+ map [string ]rules.MetadataValueRule {
144+ ".*" : metadataValueRule (false , metadataByExpression (".*" )),
145+ },
146+ map [string ]rules.MetadataValueRule {
147+ ".*" : metadataValueRule (false , metadataByExpression (".*" )),
148+ },
149+ ),
150+ ),
151+ audienceRule (
152+ rules.Audience {Kind : rules .AudienceKindUser , Name : ownerName },
153+ metadataRule (
154+ rules .ActionTypeAllow ,
155+ "v1" ,
156+ []string {"ConfigMap" },
157+ map [string ]rules.MetadataValueRule {
158+ ownerLabel : metadataValueRule (false , metadataByExact ("owner-a" )),
159+ },
160+ map [string ]rules.MetadataValueRule {
161+ ownerAnnotation : metadataValueRule (false , metadataByExact ("owner-a" )),
162+ },
163+ ),
164+ ),
165+ audienceRule (
166+ rules.Audience {Kind : rules .AudienceKindUser , Name : secondOwnerName },
167+ metadataRule (
168+ rules .ActionTypeAllow ,
169+ "v1" ,
170+ []string {"ConfigMap" },
171+ map [string ]rules.MetadataValueRule {
172+ secondOwnerLabel : metadataValueRule (false , metadataByExact ("owner-b" )),
173+ },
174+ map [string ]rules.MetadataValueRule {
175+ secondOwnerAnnotation : metadataValueRule (false , metadataByExact ("owner-b" )),
176+ },
177+ ),
178+ ),
179+ }
180+ }
181+
115182 baseTenantRules := func () []* rules.NamespaceRuleBodyTenant {
116183 return []* rules.NamespaceRuleBodyTenant {
117184 metadataRule (
@@ -190,6 +257,14 @@ var _ = Describe("enforcing generic metadata namespace rules", Ordered, Label("t
190257 },
191258 },
192259 },
260+ {
261+ CoreOwnerSpec : rbac.CoreOwnerSpec {
262+ UserSpec : rbac.UserSpec {
263+ Name : secondOwnerName ,
264+ Kind : "User" ,
265+ },
266+ },
267+ },
193268 },
194269 Rules : tenantRules ,
195270 },
@@ -574,6 +649,62 @@ var _ = Describe("enforcing generic metadata namespace rules", Ordered, Label("t
574649 }, defaultTimeoutInterval , defaultPollInterval ).Should (Succeed ())
575650 }
576651
652+ patchConfigMapMetadata := func (
653+ cs kubernetes.Interface ,
654+ nsName string ,
655+ cmName string ,
656+ labels map [string ]string ,
657+ annotations map [string ]string ,
658+ ) error {
659+ metadataPatch := map [string ]any {}
660+ if labels != nil {
661+ metadataPatch ["labels" ] = labels
662+ }
663+ if annotations != nil {
664+ metadataPatch ["annotations" ] = annotations
665+ }
666+
667+ patch , err := json .Marshal (map [string ]any {"metadata" : metadataPatch })
668+ if err != nil {
669+ return err
670+ }
671+
672+ _ , err = cs .CoreV1 ().ConfigMaps (nsName ).Patch (
673+ context .Background (),
674+ cmName ,
675+ k8stypes .MergePatchType ,
676+ patch ,
677+ metav1.PatchOptions {},
678+ )
679+
680+ return err
681+ }
682+
683+ patchConfigMapMetadataAndExpectDenied := func (
684+ cs kubernetes.Interface ,
685+ nsName string ,
686+ cmName string ,
687+ labels map [string ]string ,
688+ annotations map [string ]string ,
689+ substrings ... string ,
690+ ) {
691+ Eventually (func () error {
692+ err := patchConfigMapMetadata (cs , nsName , cmName , labels , annotations )
693+ if err == nil {
694+ return fmt .Errorf ("expected configmap metadata patch to be denied, but it succeeded" )
695+ }
696+
697+ message := err .Error ()
698+ for _ , substring := range substrings {
699+ if ! strings .Contains (message , substring ) {
700+ return fmt .Errorf ("expected error to contain %q, got: %s" , substring , message )
701+ }
702+ }
703+
704+ return nil
705+ }, defaultTimeoutInterval , defaultPollInterval ).Should (Succeed ())
706+ }
707+
577708 createServiceAndExpectAllowed := func (cs kubernetes.Interface , nsName string , svc * corev1.Service ) {
578709 EventuallyCreation (func () error {
579710 _ , err := cs .CoreV1 ().Services (nsName ).Create (context .Background (), svc , metav1.CreateOptions {})
@@ -719,6 +850,25 @@ var _ = Describe("enforcing generic metadata namespace rules", Ordered, Label("t
719850 }, defaultTimeoutInterval , defaultPollInterval ).Should (Succeed ())
720851 }
721852
853+ prepareIsolatedMetadataTest := func (name string ) (* corev1.Namespace , kubernetes.Interface , kubernetes.Interface , * corev1.ConfigMap ) {
854+ updateTenantRules (isolatedMetadataRules ())
855+
856+ ns := createNamespace (nil )
857+ waitForProjectedMetadata (ns .Name , secondOwnerLabel , nil , nil )
858+
859+ owner := ownerClient (tnt .Spec .Owners [0 ].UserSpec )
860+ secondOwner := ownerClient (tnt .Spec .Owners [1 ].UserSpec )
861+ cm := configMap (
862+ name ,
863+ map [string ]string {ownerLabel : "owner-a" },
864+ map [string ]string {ownerAnnotation : "owner-a" },
865+ )
866+
867+ createConfigMapAndExpectAllowed (owner , ns .Name , cm )
868+
869+ return ns , owner , secondOwner , cm
870+ }
871+
722872 BeforeEach (func () {
723873 tenantRules = baseTenantRules ()
724874 })
@@ -1358,6 +1508,142 @@ var _ = Describe("enforcing generic metadata namespace rules", Ordered, Label("t
13581508 )
13591509 })
13601510
1511+ It ("allows an audience to patch its metadata while another audience's metadata is unchanged" , func () {
1512+ ns , _ , secondOwner , cm := prepareIsolatedMetadataTest ("metadata-update-isolated-allowed" )
1513+
1514+ By ("patching only the label and annotation assigned to the second owner" )
1515+ Eventually (func () error {
1516+ return patchConfigMapMetadata (
1517+ secondOwner ,
1518+ ns .Name ,
1519+ cm .Name ,
1520+ map [string ]string {secondOwnerLabel : "owner-b" },
1521+ map [string ]string {secondOwnerAnnotation : "owner-b" },
1522+ )
1523+ }, defaultTimeoutInterval , defaultPollInterval ).Should (Succeed ())
1524+
1525+ By ("preserving both audiences' metadata" )
1526+ Eventually (func (g Gomega ) {
1527+ current , err := secondOwner .CoreV1 ().ConfigMaps (ns .Name ).Get (
1528+ context .Background (),
1529+ cm .Name ,
1530+ metav1.GetOptions {},
1531+ )
1532+ g .Expect (err ).NotTo (HaveOccurred ())
1533+ g .Expect (current .Labels ).To (HaveKeyWithValue (ownerLabel , "owner-a" ))
1534+ g .Expect (current .Labels ).To (HaveKeyWithValue (secondOwnerLabel , "owner-b" ))
1535+ g .Expect (current .Annotations ).To (HaveKeyWithValue (ownerAnnotation , "owner-a" ))
1536+ g .Expect (current .Annotations ).To (HaveKeyWithValue (secondOwnerAnnotation , "owner-b" ))
1537+ }, defaultTimeoutInterval , defaultPollInterval ).Should (Succeed ())
1538+ })
1539+
1540+ It ("denies patches to metadata assigned to another audience" , func () {
1541+ ns , _ , secondOwner , cm := prepareIsolatedMetadataTest ("metadata-update-other-audience-denied" )
1542+
1543+ By ("denying a change to the first owner's label" )
1544+ patchConfigMapMetadataAndExpectDenied (
1545+ secondOwner ,
1546+ ns .Name ,
1547+ cm .Name ,
1548+ map [string ]string {ownerLabel : "tampered" },
1549+ nil ,
1550+ ownerLabel ,
1551+ "tampered" ,
1552+ "denied" ,
1553+ )
1554+
1555+ By ("denying a change to the first owner's annotation" )
1556+ patchConfigMapMetadataAndExpectDenied (
1557+ secondOwner ,
1558+ ns .Name ,
1559+ cm .Name ,
1560+ nil ,
1561+ map [string ]string {ownerAnnotation : "tampered" },
1562+ ownerAnnotation ,
1563+ "tampered" ,
1564+ "denied" ,
1565+ )
1566+
1567+ By ("preserving the first owner's metadata" )
1568+ Eventually (func (g Gomega ) {
1569+ current , err := secondOwner .CoreV1 ().ConfigMaps (ns .Name ).Get (
1570+ context .Background (),
1571+ cm .Name ,
1572+ metav1.GetOptions {},
1573+ )
1574+ g .Expect (err ).NotTo (HaveOccurred ())
1575+ g .Expect (current .Labels ).To (HaveKeyWithValue (ownerLabel , "owner-a" ))
1576+ g .Expect (current .Annotations ).To (HaveKeyWithValue (ownerAnnotation , "owner-a" ))
1577+ }, defaultTimeoutInterval , defaultPollInterval ).Should (Succeed ())
1578+ })
1579+
1580+ It ("denies disallowed values for metadata assigned to the updating audience" , func () {
1581+ ns , _ , secondOwner , cm := prepareIsolatedMetadataTest ("metadata-update-own-value-denied" )
1582+
1583+ By ("denying a disallowed value for the second owner's label" )
1584+ patchConfigMapMetadataAndExpectDenied (
1585+ secondOwner ,
1586+ ns .Name ,
1587+ cm .Name ,
1588+ map [string ]string {secondOwnerLabel : "blocked" },
1589+ nil ,
1590+ secondOwnerLabel ,
1591+ "blocked" ,
1592+ "denied" ,
1593+ )
1594+
1595+ By ("denying a disallowed value for the second owner's annotation" )
1596+ patchConfigMapMetadataAndExpectDenied (
1597+ secondOwner ,
1598+ ns .Name ,
1599+ cm .Name ,
1600+ nil ,
1601+ map [string ]string {secondOwnerAnnotation : "blocked" },
1602+ secondOwnerAnnotation ,
1603+ "blocked" ,
1604+ "denied" ,
1605+ )
1606+ })
1607+
1608+ It ("allows non-metadata updates when another audience's metadata is unchanged" , func () {
1609+ ns , _ , secondOwner , cm := prepareIsolatedMetadataTest ("metadata-update-data-allowed" )
1610+
1611+ By ("updating ConfigMap data as the second owner" )
1612+ Eventually (func () error {
1613+ current , err := secondOwner .CoreV1 ().ConfigMaps (ns .Name ).Get (
1614+ context .Background (),
1615+ cm .Name ,
1616+ metav1.GetOptions {},
1617+ )
1618+ if err != nil {
1619+ return err
1620+ }
1621+
1622+ current .Data ["key" ] = "updated"
1623+
1624+ _ , err = secondOwner .CoreV1 ().ConfigMaps (ns .Name ).Update (
1625+ context .Background (),
1626+ current ,
1627+ metav1.UpdateOptions {},
1628+ )
1629+
1630+ return err
1631+ }, defaultTimeoutInterval , defaultPollInterval ).Should (Succeed ())
1632+
1633+ By ("preserving the metadata and the data update" )
1634+ Eventually (func (g Gomega ) {
1635+ current , err := secondOwner .CoreV1 ().ConfigMaps (ns .Name ).Get (
1636+ context .Background (),
1637+ cm .Name ,
1638+ metav1.GetOptions {},
1639+ )
1640+ g .Expect (err ).NotTo (HaveOccurred ())
1641+ g .Expect (current .Data ).To (HaveKeyWithValue ("key" , "updated" ))
1642+ g .Expect (current .Labels ).To (HaveKeyWithValue (ownerLabel , "owner-a" ))
1643+ g .Expect (current .Annotations ).To (HaveKeyWithValue (ownerAnnotation , "owner-a" ))
1644+ }, defaultTimeoutInterval , defaultPollInterval ).Should (Succeed ())
1645+ })
1646+
13611647 It ("denies an update when a metadata value becomes invalid" , func () {
13621648 updateTenantRules ([]* rules.NamespaceRuleBodyTenant {
13631649 metadataRule (
0 commit comments