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  }