github.com/treeverse/lakefs@v1.24.1-0.20240520134607-95648127bfb0/pkg/kv/migrations/migrations_test.go (about) 1 package migrations_test 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "strings" 8 "testing" 9 "time" 10 11 "github.com/go-test/deep" 12 "github.com/stretchr/testify/require" 13 "github.com/treeverse/lakefs/pkg/auth" 14 "github.com/treeverse/lakefs/pkg/auth/acl" 15 "github.com/treeverse/lakefs/pkg/auth/model" 16 "github.com/treeverse/lakefs/pkg/auth/setup" 17 authtestutil "github.com/treeverse/lakefs/pkg/auth/testutil" 18 "github.com/treeverse/lakefs/pkg/config" 19 "github.com/treeverse/lakefs/pkg/kv/migrations" 20 "github.com/treeverse/lakefs/pkg/permissions" 21 "github.com/treeverse/lakefs/pkg/testutil" 22 "golang.org/x/exp/slices" 23 ) 24 25 func TestGetMinPermission(t *testing.T) { 26 tests := []struct { 27 Action string 28 Permission model.ACLPermission 29 }{ 30 {Action: permissions.ReadObjectAction, Permission: acl.ReadPermission}, 31 {Action: "fs:Read*", Permission: acl.ReadPermission}, 32 {Action: "fs:ReadO*", Permission: acl.ReadPermission}, 33 {Action: permissions.ListObjectsAction, Permission: acl.ReadPermission}, 34 {Action: "fs:List*", Permission: acl.ReadPermission}, 35 {Action: permissions.ReadActionsAction, Permission: acl.WritePermission}, 36 {Action: "fs:WriteO?ject", Permission: acl.WritePermission}, 37 } 38 39 mig := migrations.NewACLsMigrator(nil, false) 40 41 for _, tt := range tests { 42 t.Run(tt.Action, func(t *testing.T) { 43 permission := mig.GetMinPermission(tt.Action) 44 if permission != tt.Permission { 45 t.Errorf("Got permission %s != %s", permission, tt.Permission) 46 } 47 }) 48 } 49 } 50 51 func TestComputePermission(t *testing.T) { 52 tests := []struct { 53 Name string 54 Actions []string 55 56 Permission model.ACLPermission 57 Err error 58 }{ 59 { 60 Name: "read-all", 61 Actions: auth.GetActionsForPolicyTypeOrDie("FSRead"), 62 Permission: acl.ReadPermission, 63 }, { 64 Name: "read-one", 65 Actions: []string{permissions.ReadRepositoryAction}, 66 Permission: acl.ReadPermission, 67 }, { 68 Name: "read-two", 69 Actions: []string{permissions.ListObjectsAction, permissions.ReadTagAction}, 70 Permission: acl.ReadPermission, 71 }, { 72 Name: "only-own-credentials", 73 Actions: auth.GetActionsForPolicyTypeOrDie("AuthManageOwnCredentials"), 74 Permission: acl.ReadPermission, 75 }, { 76 Name: "write-all", 77 Actions: auth.GetActionsForPolicyTypeOrDie("FSReadWrite"), 78 Permission: acl.WritePermission, 79 }, { 80 Name: "write-one", 81 Actions: []string{permissions.WriteObjectAction}, 82 Permission: acl.WritePermission, 83 }, { 84 Name: "write-one-read-one-create-one", 85 Actions: []string{permissions.CreateCommitAction, permissions.ReadObjectAction, permissions.CreateMetaRangeAction}, 86 Permission: acl.WritePermission, 87 }, { 88 Name: "super-all", 89 Actions: auth.GetActionsForPolicyTypeOrDie("FSFullAccess"), 90 Permission: acl.SuperPermission, 91 }, { 92 Name: "super-one", 93 Actions: []string{permissions.AttachStorageNamespaceAction}, 94 Permission: acl.SuperPermission, 95 }, { 96 Name: "super-one-write-one-read-two", 97 Actions: []string{permissions.CreateTagAction, permissions.AttachStorageNamespaceAction, permissions.ReadConfigAction, permissions.ReadRepositoryAction}, 98 Permission: acl.SuperPermission, 99 }, { 100 Name: "admin-all", 101 Actions: auth.GetActionsForPolicyTypeOrDie("AllAccess"), 102 Permission: acl.AdminPermission, 103 }, { 104 Name: "admin-one", 105 Actions: []string{permissions.SetGarbageCollectionRulesAction}, 106 Permission: acl.AdminPermission, 107 }, 108 } 109 110 ctx := context.Background() 111 112 for _, tt := range tests { 113 t.Run(tt.Name, func(t *testing.T) { 114 mig := migrations.NewACLsMigrator(nil, false) 115 116 permission, err := mig.ComputePermission(ctx, tt.Actions) 117 if !errors.Is(err, tt.Err) { 118 t.Errorf("Got error %s but expected %s", err, tt.Err) 119 } 120 if permission != tt.Permission { 121 t.Errorf("Got permission %s when expecting %s", permission, tt.Permission) 122 } 123 }) 124 } 125 } 126 127 func TestBroaderPermission(t *testing.T) { 128 perms := []model.ACLPermission{"", acl.ReadPermission, acl.WritePermission, acl.SuperPermission, acl.AdminPermission} 129 for i, a := range perms { 130 for j, b := range perms { 131 after := i > j 132 broader := migrations.BroaderPermission(a, b) 133 if after != broader { 134 if after { 135 t.Error("Expected broader permission") 136 } else { 137 t.Error("Expected not a broader permission") 138 } 139 } 140 } 141 } 142 } 143 144 func getPolicy(t *testing.T, ctx context.Context, svc auth.Service, name string) *model.Policy { 145 t.Helper() 146 policy, err := svc.GetPolicy(ctx, name) 147 if err != nil { 148 t.Fatal(err) 149 } 150 return policy 151 } 152 153 func getPolicies(t *testing.T, ctx context.Context, svc auth.Service, name string) []*model.Policy { 154 t.Helper() 155 policies, _, err := svc.ListGroupPolicies(ctx, name, &model.PaginationParams{Amount: 1000}) 156 if err != nil { 157 t.Fatal(err) 158 } 159 return policies 160 } 161 162 func TestNewACLForPolicies_Generator(t *testing.T) { 163 now := time.Now() 164 ctx := context.Background() 165 svc, _ := authtestutil.SetupService(t, ctx, []byte("shh...")) 166 167 err := setup.CreateRBACBaseGroups(ctx, svc, now) 168 if err != nil { 169 t.Fatal(err) 170 } 171 172 tests := []struct { 173 Name string 174 // Policies are passed to NewACLForPolicies 175 Policies []*model.Policy 176 // ACL is expected to be returned by NewACLForPolicies 177 ACL model.ACL 178 // Err, if set, is expected to be the error returned from NewACLForPolicies 179 Err error 180 }{ 181 { 182 Name: "ExactlyFSFullAccess", 183 Policies: []*model.Policy{getPolicy(t, ctx, svc, "FSFullAccess")}, 184 ACL: model.ACL{ 185 Permission: acl.SuperPermission, 186 }, 187 }, { 188 Name: "GroupSuperUsers", 189 Policies: getPolicies(t, ctx, svc, "SuperUsers"), 190 ACL: model.ACL{ 191 Permission: acl.SuperPermission, 192 }, 193 }, { 194 Name: "ExactlyFSReadAll", 195 Policies: []*model.Policy{getPolicy(t, ctx, svc, "FSReadAll")}, 196 ACL: model.ACL{ 197 Permission: acl.ReadPermission, 198 }, 199 }, { 200 Name: "GroupViewers", 201 Policies: getPolicies(t, ctx, svc, "Viewers"), 202 ACL: model.ACL{ 203 Permission: acl.ReadPermission, 204 }, 205 }, 206 } 207 208 mig := migrations.NewACLsMigrator(svc, false) 209 210 for _, tt := range tests { 211 t.Run(tt.Name, func(t *testing.T) { 212 acp, _, err := mig.NewACLForPolicies(ctx, tt.Policies) 213 if !errors.Is(err, tt.Err) { 214 t.Errorf("Got error %s, expected %s", err, tt.Err) 215 } 216 if diffs := deep.Equal(acp, &tt.ACL); diffs != nil { 217 t.Errorf("Bad ACL: %s", diffs) 218 } 219 }) 220 } 221 } 222 223 func TestMigrateImportPermissions(t *testing.T) { 224 ctx := context.Background() 225 cfg := config.Config{} 226 227 tests := []struct { 228 name string 229 policies []model.Policy 230 uiAuthType string 231 }{ 232 { 233 name: "eternal_auth_type", 234 policies: []model.Policy{ 235 { 236 CreatedAt: time.Now().Add(-time.Hour).UTC(), 237 DisplayName: "import_no_change", 238 Statement: []model.Statement{ 239 { 240 Effect: "allow", 241 Action: []string{permissions.ImportFromStorageAction, permissions.CreatePolicyAction}, 242 Resource: createARN("importFromStorage"), 243 }, 244 }, 245 }, 246 }, 247 uiAuthType: config.AuthRBACExternal, 248 }, 249 { 250 name: "empty", 251 policies: []model.Policy{}, 252 uiAuthType: config.AuthRBACSimplified, 253 }, 254 { 255 name: "no_import", 256 policies: []model.Policy{ 257 { 258 CreatedAt: time.Now().Add(-time.Hour).UTC(), 259 DisplayName: "policy1", 260 Statement: []model.Statement{ 261 { 262 Effect: "allow", 263 Action: []string{permissions.CreateBranchAction, permissions.CreatePolicyAction}, 264 Resource: createARN("policy1"), 265 }, 266 }, 267 }, 268 { 269 CreatedAt: time.Now().Add(-time.Hour).UTC(), 270 DisplayName: "policy2", 271 Statement: []model.Statement{ 272 { 273 Effect: "deny", 274 Action: []string{permissions.CreateUserAction}, 275 Resource: createARN("policy2"), 276 }, 277 }, 278 }, 279 }, 280 uiAuthType: config.AuthRBACSimplified, 281 }, 282 { 283 name: "basic", 284 policies: []model.Policy{ 285 { 286 CreatedAt: time.Now().Add(-time.Hour).UTC(), 287 DisplayName: "import1", 288 Statement: []model.Statement{ 289 { 290 Effect: "allow", 291 Action: []string{permissions.ImportFromStorageAction, permissions.CreatePolicyAction}, 292 Resource: createARN("importFromStorage"), 293 }, 294 }, 295 }, 296 { 297 CreatedAt: time.Now().Add(-time.Hour).UTC(), 298 DisplayName: "no_import", 299 Statement: []model.Statement{ 300 { 301 Effect: "deny", 302 Action: []string{permissions.RevertBranchAction}, 303 Resource: createARN("RevertBranchAction"), 304 }, 305 }, 306 }, 307 { 308 CreatedAt: time.Now().Add(-time.Hour).UTC(), 309 DisplayName: "import2", 310 Statement: []model.Statement{ 311 { 312 Effect: "allow", 313 Action: []string{permissions.AddGroupMemberAction, permissions.ImportFromStorageAction, permissions.CreateUserAction}, 314 Resource: createARN("importFromStorage2"), 315 }, 316 { 317 Effect: "deny", 318 Action: []string{permissions.CreateBranchAction}, 319 Resource: createARN("CreateBranchAction"), 320 }, 321 { 322 Effect: "deny", 323 Action: []string{permissions.ImportFromStorageAction, permissions.CreateCommitAction}, 324 Resource: createARN("importFromStorage3"), 325 }, 326 }, 327 }, 328 }, 329 uiAuthType: config.AuthRBACSimplified, 330 }, 331 } 332 333 for _, tt := range tests { 334 t.Run(tt.name, func(t *testing.T) { 335 authService, store := authtestutil.SetupService(t, ctx, []byte("some secret")) 336 for _, policy := range tt.policies { 337 testutil.MustDo(t, "create Policy", authService.WritePolicy(ctx, &policy, false)) 338 } 339 // Run migrate 340 cfg.Auth.UIConfig.RBAC = tt.uiAuthType 341 testutil.MustDo(t, "migrate", migrations.MigrateImportPermissions(ctx, store, &cfg)) 342 343 // Verify 344 verifyMigration(t, ctx, authService, tt.policies, cfg) 345 }) 346 } 347 } 348 349 func createARN(name string) string { 350 return fmt.Sprintf("arn:%s:this:is:an:arn", name) 351 } 352 353 func verifyMigration(t *testing.T, ctx context.Context, authService *auth.AuthService, policies []model.Policy, cfg config.Config) { 354 for _, prev := range policies { 355 policy, err := authService.GetPolicy(ctx, prev.DisplayName) 356 testutil.MustDo(t, "get policy", err) 357 358 require.Equal(t, prev.ACL, policy.ACL) 359 if strings.HasPrefix(policy.DisplayName, "import") { 360 expected := prev.Statement 361 if cfg.IsAuthUISimplified() { 362 require.Greater(t, policy.CreatedAt, prev.CreatedAt) 363 for _, statement := range expected { 364 for { 365 idx := slices.Index(statement.Action, permissions.ImportFromStorageAction) 366 if idx < 0 { 367 break 368 } 369 statement.Action[idx] = "fs:Import*" 370 } 371 } 372 } 373 require.Equal(t, expected, policy.Statement) 374 } else { 375 require.Equal(t, prev.CreatedAt, policy.CreatedAt) 376 require.Equal(t, prev.Statement, policy.Statement) 377 } 378 } 379 }