go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/swarming/server/acls/acls_test.go (about)

     1  // Copyright 2023 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package acls
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"sort"
    21  	"strings"
    22  	"testing"
    23  
    24  	"google.golang.org/grpc/codes"
    25  	"google.golang.org/protobuf/encoding/prototext"
    26  	"google.golang.org/protobuf/proto"
    27  
    28  	"go.chromium.org/luci/auth/identity"
    29  	"go.chromium.org/luci/common/data/stringset"
    30  	"go.chromium.org/luci/config"
    31  	"go.chromium.org/luci/config/cfgclient"
    32  	cfgmem "go.chromium.org/luci/config/impl/memory"
    33  	"go.chromium.org/luci/gae/impl/memory"
    34  	"go.chromium.org/luci/server/auth/authtest"
    35  	"go.chromium.org/luci/server/auth/realms"
    36  
    37  	configpb "go.chromium.org/luci/swarming/proto/config"
    38  	"go.chromium.org/luci/swarming/server/cfg"
    39  
    40  	. "github.com/smartystreets/goconvey/convey"
    41  	. "go.chromium.org/luci/common/testing/assertions"
    42  )
    43  
    44  func TestServerLevel(t *testing.T) {
    45  	t.Parallel()
    46  
    47  	ctx := context.Background()
    48  
    49  	cfg := mockedConfig(&configpb.AuthSettings{
    50  		AdminsGroup:          "admins",
    51  		BotBootstrapGroup:    "bootstrap",
    52  		PrivilegedUsersGroup: "privileged",
    53  		ViewAllBotsGroup:     "view-all-bots",
    54  		ViewAllTasksGroup:    "view-all-tasks",
    55  	}, nil, nil)
    56  
    57  	db := authtest.NewFakeDB(
    58  		authtest.MockMembership("user:admin@example.com", "admins"),
    59  		authtest.MockMembership("user:bootstrap@example.com", "bootstrap"),
    60  		authtest.MockMembership("user:privileged@example.com", "privileged"),
    61  		authtest.MockMembership("user:view-all-bots@example.com", "view-all-bots"),
    62  		authtest.MockMembership("user:view-all-tasks@example.com", "view-all-tasks"),
    63  	)
    64  
    65  	permittedPerms := func(caller identity.Identity) []realms.Permission {
    66  		chk := Checker{cfg: cfg, db: db, caller: caller}
    67  		var permitted []realms.Permission
    68  		for _, perm := range allPermissions() {
    69  			res := chk.CheckServerPerm(ctx, perm)
    70  			So(res.InternalError, ShouldBeFalse)
    71  			if res.Permitted {
    72  				permitted = append(permitted, perm)
    73  			}
    74  		}
    75  		return permitted
    76  	}
    77  
    78  	Convey("Unknown", t, func() {
    79  		So(permittedPerms("user:unknown@example.com"), ShouldBeEmpty)
    80  	})
    81  
    82  	Convey("Admin", t, func() {
    83  		assertSame(permittedPerms("user:admin@example.com"), []realms.Permission{
    84  			PermTasksGet,
    85  			PermTasksCancel,
    86  			PermPoolsListBots,
    87  			PermPoolsListTasks,
    88  			PermPoolsCreateBot,
    89  			PermPoolsDeleteBot,
    90  			PermPoolsTerminateBot,
    91  			PermPoolsCancelTask,
    92  		})
    93  	})
    94  
    95  	Convey("Bootstrap", t, func() {
    96  		assertSame(permittedPerms("user:bootstrap@example.com"), []realms.Permission{
    97  			PermPoolsCreateBot,
    98  		})
    99  	})
   100  
   101  	Convey("Privileged", t, func() {
   102  		assertSame(permittedPerms("user:privileged@example.com"), []realms.Permission{
   103  			PermTasksGet,
   104  			PermPoolsListBots,
   105  			PermPoolsListTasks,
   106  		})
   107  	})
   108  
   109  	Convey("View all bots", t, func() {
   110  		assertSame(permittedPerms("user:view-all-bots@example.com"), []realms.Permission{
   111  			PermPoolsListBots,
   112  		})
   113  	})
   114  
   115  	Convey("View all tasks", t, func() {
   116  		assertSame(permittedPerms("user:view-all-tasks@example.com"), []realms.Permission{
   117  			PermTasksGet,
   118  			PermPoolsListTasks,
   119  		})
   120  	})
   121  
   122  	Convey("Error message", t, func() {
   123  		chk := Checker{cfg: cfg, db: db, caller: "user:unknown@example.com"}
   124  		res := chk.CheckServerPerm(ctx, PermTasksCancel)
   125  		So(res.Permitted, ShouldBeFalse)
   126  		err := res.ToGrpcErr()
   127  		So(err, ShouldHaveGRPCStatus, codes.PermissionDenied)
   128  		So(err, ShouldErrLike, `the caller "user:unknown@example.com" doesn't have server-level permission "swarming.tasks.cancel"`)
   129  	})
   130  }
   131  
   132  func TestPoolLevel(t *testing.T) {
   133  	const (
   134  		unknownID    identity.Identity = "user:unknown@example.com"
   135  		privilegedID identity.Identity = "user:privileged@example.com"
   136  		authorizedID identity.Identity = "user:authorized@example.com"
   137  		anotherID    identity.Identity = "user:another@example.com"
   138  	)
   139  
   140  	t.Parallel()
   141  
   142  	ctx := context.Background()
   143  
   144  	allPools := []string{
   145  		"visible-pool-1",
   146  		"visible-pool-2",
   147  		"hidden-pool-1",
   148  		"hidden-pool-2",
   149  		"deleted-pool-1", // has no realm association
   150  		"deleted-pool-2", // has no realm association
   151  	}
   152  
   153  	cfg := mockedConfig(&configpb.AuthSettings{
   154  		PrivilegedUsersGroup: "privileged",
   155  	}, map[string]string{
   156  		"visible-pool-1": "project:visible-realm",
   157  		"visible-pool-2": "project:visible-realm",
   158  		"hidden-pool-1":  "project:hidden-realm",
   159  		"hidden-pool-2":  "project:hidden-realm",
   160  	}, nil)
   161  
   162  	db := authtest.NewFakeDB(
   163  		authtest.MockMembership(privilegedID, "privileged"),
   164  		authtest.MockPermission(authorizedID, "project:visible-realm", PermPoolsListBots),
   165  		authtest.MockPermission(anotherID, "project:hidden-realm", PermPoolsListBots),
   166  	)
   167  
   168  	Convey("CheckPoolPerm", t, func() {
   169  		// Note the implementation doesn't depend on exact permission being checked,
   170  		// we'll check only PermPoolsListBots.
   171  		poolsWithListBots := func(caller identity.Identity) []string {
   172  			chk := Checker{cfg: cfg, db: db, caller: caller}
   173  			var pools []string
   174  			for _, pool := range allPools {
   175  				res := chk.CheckPoolPerm(ctx, pool, PermPoolsListBots)
   176  				So(res.InternalError, ShouldBeFalse)
   177  				if res.Permitted {
   178  					pools = append(pools, pool)
   179  				}
   180  			}
   181  			return pools
   182  		}
   183  
   184  		Convey("Unknown", func() {
   185  			So(poolsWithListBots(unknownID), ShouldBeEmpty)
   186  		})
   187  
   188  		Convey("Privileged", func() {
   189  			So(poolsWithListBots(privilegedID), ShouldResemble, allPools)
   190  		})
   191  
   192  		Convey("Authorized", func() {
   193  			So(poolsWithListBots(authorizedID), ShouldResemble, []string{
   194  				"visible-pool-1",
   195  				"visible-pool-2",
   196  			})
   197  		})
   198  
   199  		Convey("Error message", func() {
   200  			chk := Checker{cfg: cfg, db: db, caller: unknownID}
   201  			for _, pool := range allPools {
   202  				res := chk.CheckPoolPerm(ctx, pool, PermPoolsListBots)
   203  				So(res.Permitted, ShouldBeFalse)
   204  				err := res.ToGrpcErr()
   205  				So(err, ShouldHaveGRPCStatus, codes.PermissionDenied)
   206  				So(err, ShouldErrLike,
   207  					fmt.Sprintf(`the caller "user:unknown@example.com" doesn't have permission "swarming.pools.listBots"`+
   208  						` in the pool %q or the pool doesn't exist`, pool))
   209  			}
   210  		})
   211  	})
   212  
   213  	Convey("FilterPoolsByPerm", t, func() {
   214  		poolsWithListBots := func(caller identity.Identity) []string {
   215  			chk := Checker{cfg: cfg, db: db, caller: caller}
   216  			pools, err := chk.FilterPoolsByPerm(ctx, allPools, PermPoolsListBots)
   217  			So(err, ShouldBeNil)
   218  			return pools
   219  		}
   220  
   221  		Convey("Unknown", func() {
   222  			So(poolsWithListBots(unknownID), ShouldBeEmpty)
   223  		})
   224  
   225  		Convey("Privileged", func() {
   226  			So(poolsWithListBots(privilegedID), ShouldResemble, allPools)
   227  		})
   228  
   229  		Convey("Authorized", func() {
   230  			So(poolsWithListBots(authorizedID), ShouldResemble, []string{
   231  				"visible-pool-1",
   232  				"visible-pool-2",
   233  			})
   234  		})
   235  	})
   236  
   237  	Convey("CheckAllPoolsPerm", t, func() {
   238  		checkAll := func(caller identity.Identity, pools []string) bool {
   239  			chk := Checker{cfg: cfg, db: db, caller: caller}
   240  			res := chk.CheckAllPoolsPerm(ctx, pools, PermPoolsListBots)
   241  			So(res.InternalError, ShouldBeFalse)
   242  			return res.Permitted
   243  		}
   244  
   245  		Convey("Unknown", func() {
   246  			So(checkAll(unknownID, []string{"visible-pool-1"}), ShouldBeFalse)
   247  			So(checkAll(unknownID, []string{"visible-pool-1", "visible-pool-2"}), ShouldBeFalse)
   248  			So(checkAll(unknownID, allPools), ShouldBeFalse)
   249  		})
   250  
   251  		Convey("Privileged", func() {
   252  			So(checkAll(privilegedID, []string{"visible-pool-1"}), ShouldBeTrue)
   253  			So(checkAll(privilegedID, []string{"visible-pool-1", "visible-pool-2"}), ShouldBeTrue)
   254  			So(checkAll(privilegedID, []string{"hidden-pool-1"}), ShouldBeTrue)
   255  			So(checkAll(privilegedID, []string{"hidden-pool-1", "hidden-pool-2"}), ShouldBeTrue)
   256  			So(checkAll(privilegedID, allPools), ShouldBeTrue)
   257  		})
   258  
   259  		Convey("Authorized", func() {
   260  			So(checkAll(authorizedID, []string{"visible-pool-1"}), ShouldBeTrue)
   261  			So(checkAll(authorizedID, []string{"visible-pool-1", "visible-pool-2"}), ShouldBeTrue)
   262  			So(checkAll(authorizedID, []string{"hidden-pool-1"}), ShouldBeFalse)
   263  			So(checkAll(authorizedID, []string{"hidden-pool-1", "hidden-pool-2"}), ShouldBeFalse)
   264  			So(checkAll(authorizedID, []string{"visible-pool-1", "visible-pool-2", "hidden-pool-1"}), ShouldBeFalse)
   265  			So(checkAll(authorizedID, []string{"visible-pool-1", "visible-pool-2", "deleted-pool-1"}), ShouldBeFalse)
   266  		})
   267  
   268  		Convey("Error message", func() {
   269  			chk := Checker{cfg: cfg, db: db, caller: authorizedID}
   270  			res := chk.CheckAllPoolsPerm(ctx, allPools, PermPoolsListBots)
   271  			So(res.InternalError, ShouldBeFalse)
   272  			err := res.ToGrpcErr()
   273  			So(err, ShouldHaveGRPCStatus, codes.PermissionDenied)
   274  			So(err, ShouldErrLike, `the caller "user:authorized@example.com" doesn't have permission "swarming.pools.listBots" in some of the requested pools`)
   275  		})
   276  	})
   277  
   278  	Convey("CheckAnyPoolsPerm", t, func() {
   279  		checkAny := func(caller identity.Identity, pools []string) bool {
   280  			chk := Checker{cfg: cfg, db: db, caller: caller}
   281  			res := chk.CheckAnyPoolsPerm(ctx, pools, PermPoolsListBots)
   282  			So(res.InternalError, ShouldBeFalse)
   283  			return res.Permitted
   284  		}
   285  
   286  		Convey("Unknown", func() {
   287  			So(checkAny(unknownID, []string{"visible-pool-1"}), ShouldBeFalse)
   288  			So(checkAny(unknownID, []string{"visible-pool-1", "visible-pool-2"}), ShouldBeFalse)
   289  			So(checkAny(unknownID, allPools), ShouldBeFalse)
   290  		})
   291  
   292  		Convey("Privileged", func() {
   293  			So(checkAny(privilegedID, []string{"visible-pool-1"}), ShouldBeTrue)
   294  			So(checkAny(privilegedID, []string{"visible-pool-1", "visible-pool-2"}), ShouldBeTrue)
   295  			So(checkAny(privilegedID, []string{"hidden-pool-1"}), ShouldBeTrue)
   296  			So(checkAny(privilegedID, []string{"hidden-pool-1", "hidden-pool-2"}), ShouldBeTrue)
   297  			So(checkAny(privilegedID, allPools), ShouldBeTrue)
   298  		})
   299  
   300  		Convey("Authorized", func() {
   301  			So(checkAny(authorizedID, []string{"visible-pool-1"}), ShouldBeTrue)
   302  			So(checkAny(authorizedID, []string{"visible-pool-1", "visible-pool-2"}), ShouldBeTrue)
   303  			So(checkAny(authorizedID, []string{"hidden-pool-1"}), ShouldBeFalse)
   304  			So(checkAny(authorizedID, []string{"hidden-pool-1", "hidden-pool-2"}), ShouldBeFalse)
   305  			So(checkAny(authorizedID, []string{"deleted-pool-1"}), ShouldBeFalse)
   306  			So(checkAny(authorizedID, []string{"deleted-pool-1", "deleted-pool-2"}), ShouldBeFalse)
   307  			So(checkAny(authorizedID, []string{"hidden-pool-1", "visible-pool-1"}), ShouldBeTrue)
   308  			So(checkAny(authorizedID, []string{"deleted-pool-1", "visible-pool-1"}), ShouldBeTrue)
   309  		})
   310  
   311  		Convey("Error message", func() {
   312  			chk := Checker{cfg: cfg, db: db, caller: authorizedID}
   313  			res := chk.CheckAnyPoolsPerm(ctx, []string{"hidden-pool-1", "deleted-pool-1"}, PermPoolsListBots)
   314  			So(res.InternalError, ShouldBeFalse)
   315  			err := res.ToGrpcErr()
   316  			So(err, ShouldHaveGRPCStatus, codes.PermissionDenied)
   317  			So(err, ShouldErrLike, `the caller "user:authorized@example.com" doesn't have permission "swarming.pools.listBots" in any of the requested pools`)
   318  		})
   319  	})
   320  }
   321  
   322  func TestBotLevel(t *testing.T) {
   323  	const (
   324  		unknownID    identity.Identity = "user:unknown@example.com"
   325  		privilegedID identity.Identity = "user:privileged@example.com"
   326  		authorizedID identity.Identity = "user:authorized@example.com"
   327  	)
   328  
   329  	t.Parallel()
   330  
   331  	ctx := context.Background()
   332  
   333  	cfg := mockedConfig(&configpb.AuthSettings{
   334  		PrivilegedUsersGroup: "privileged",
   335  	}, map[string]string{
   336  		"visible-pool": "project:visible-realm",
   337  		"hidden-pool":  "project:hidden-realm",
   338  	}, map[string][]string{
   339  		"visible-bot": {"visible-pool", "hidden-pool"},
   340  		"hidden-bot":  {"hidden-pool"},
   341  	})
   342  
   343  	db := authtest.NewFakeDB(
   344  		authtest.MockMembership(privilegedID, "privileged"),
   345  		authtest.MockPermission(authorizedID, "project:visible-realm", PermPoolsListBots),
   346  	)
   347  
   348  	checkBotVisible := func(caller identity.Identity, botID string) bool {
   349  		chk := Checker{cfg: cfg, db: db, caller: caller}
   350  		res := chk.CheckBotPerm(ctx, botID, PermPoolsListBots)
   351  		So(res.InternalError, ShouldBeFalse)
   352  		return res.Permitted
   353  	}
   354  
   355  	Convey("Unknown", t, func() {
   356  		So(checkBotVisible(unknownID, "visible-bot"), ShouldBeFalse)
   357  		So(checkBotVisible(unknownID, "hidden-bot"), ShouldBeFalse)
   358  		So(checkBotVisible(unknownID, "unknown-bot"), ShouldBeFalse)
   359  	})
   360  
   361  	Convey("Privileged", t, func() {
   362  		So(checkBotVisible(privilegedID, "visible-bot"), ShouldBeTrue)
   363  		So(checkBotVisible(privilegedID, "hidden-bot"), ShouldBeTrue)
   364  		So(checkBotVisible(privilegedID, "unknown-bot"), ShouldBeTrue)
   365  	})
   366  
   367  	Convey("Authorized", t, func() {
   368  		So(checkBotVisible(authorizedID, "visible-bot"), ShouldBeTrue)
   369  		So(checkBotVisible(authorizedID, "hidden-bot"), ShouldBeFalse)
   370  		So(checkBotVisible(authorizedID, "unknown-bot"), ShouldBeFalse)
   371  	})
   372  
   373  	Convey("Error message", t, func() {
   374  		chk := Checker{cfg: cfg, db: db, caller: authorizedID}
   375  		res := chk.CheckBotPerm(ctx, "hidden-bot", PermPoolsListBots)
   376  		So(res.InternalError, ShouldBeFalse)
   377  		err := res.ToGrpcErr()
   378  		So(err, ShouldHaveGRPCStatus, codes.PermissionDenied)
   379  		So(err, ShouldErrLike, `the caller "user:authorized@example.com" doesn't have permission `+
   380  			`"swarming.pools.listBots" in the pool that contains bot "hidden-bot" or this bot doesn't exist`)
   381  	})
   382  }
   383  
   384  func TestTaskLevel(t *testing.T) {
   385  	const (
   386  		unknownID    identity.Identity = "user:unknown@example.com"
   387  		adminID      identity.Identity = "user:admin@example.com"
   388  		authorizedID identity.Identity = "user:authorized@example.com"
   389  	)
   390  
   391  	t.Parallel()
   392  
   393  	ctx := context.Background()
   394  
   395  	cfg := mockedConfig(&configpb.AuthSettings{
   396  		AdminsGroup: "admins",
   397  	}, map[string]string{
   398  		"visible-pool": "project:visible-pool-realm",
   399  		"hidden-pool":  "project:hidden-pool-realm",
   400  	}, map[string][]string{
   401  		"visible-bot": {"visible-pool", "hidden-pool"},
   402  		"hidden-bot":  {"hidden-pool"},
   403  	})
   404  
   405  	db := authtest.NewFakeDB(
   406  		authtest.MockMembership(adminID, "admins"),
   407  		authtest.MockPermission(authorizedID, "project:visible-task-realm", PermTasksCancel),
   408  		authtest.MockPermission(authorizedID, "project:visible-pool-realm", PermPoolsCancelTask),
   409  	)
   410  
   411  	checkCanCancel := func(caller identity.Identity, info TaskAuthInfo) bool {
   412  		info.TaskID = "65aba3a3e6b99310"
   413  		chk := Checker{cfg: cfg, db: db, caller: caller}
   414  		res := chk.CheckTaskPerm(ctx, info, PermTasksCancel)
   415  		So(res.InternalError, ShouldBeFalse)
   416  		return res.Permitted
   417  	}
   418  
   419  	Convey("Unknown", t, func() {
   420  		So(checkCanCancel(unknownID, TaskAuthInfo{
   421  			Realm:     "project:visible-task-realm",
   422  			Pool:      "visible-pool",
   423  			Submitter: authorizedID,
   424  		}), ShouldBeFalse)
   425  	})
   426  
   427  	Convey("Submitter", t, func() {
   428  		So(checkCanCancel(unknownID, TaskAuthInfo{
   429  			Realm:     "project:doesnt-matter",
   430  			Pool:      "doesnt-matter",
   431  			Submitter: unknownID,
   432  		}), ShouldBeTrue)
   433  	})
   434  
   435  	Convey("Admin", t, func() {
   436  		So(checkCanCancel(adminID, TaskAuthInfo{
   437  			Realm:     "project:doesnt-matter",
   438  			Pool:      "doesnt-matter",
   439  			Submitter: unknownID,
   440  		}), ShouldBeTrue)
   441  	})
   442  
   443  	Convey("Via task realm", t, func() {
   444  		So(checkCanCancel(authorizedID, TaskAuthInfo{
   445  			Realm:     "project:visible-task-realm",
   446  			Pool:      "doesnt-matter",
   447  			Submitter: unknownID,
   448  		}), ShouldBeTrue)
   449  
   450  		So(checkCanCancel(authorizedID, TaskAuthInfo{
   451  			Realm:     "project:hidden-task-realm",
   452  			Pool:      "doesnt-matter",
   453  			Submitter: unknownID,
   454  		}), ShouldBeFalse)
   455  	})
   456  
   457  	Convey("Via pool realm", t, func() {
   458  		So(checkCanCancel(authorizedID, TaskAuthInfo{
   459  			Realm:     "project:doesnt-matter",
   460  			Pool:      "visible-pool",
   461  			Submitter: unknownID,
   462  		}), ShouldBeTrue)
   463  
   464  		So(checkCanCancel(authorizedID, TaskAuthInfo{
   465  			Realm:     "project:doesnt-matter",
   466  			Pool:      "hidden-pool",
   467  			Submitter: unknownID,
   468  		}), ShouldBeFalse)
   469  	})
   470  
   471  	Convey("Via bot realm", t, func() {
   472  		So(checkCanCancel(authorizedID, TaskAuthInfo{
   473  			Realm:     "project:doesnt-matter",
   474  			BotID:     "visible-bot",
   475  			Submitter: unknownID,
   476  		}), ShouldBeTrue)
   477  
   478  		So(checkCanCancel(authorizedID, TaskAuthInfo{
   479  			Realm:     "project:doesnt-matter",
   480  			BotID:     "hidden-bot",
   481  			Submitter: unknownID,
   482  		}), ShouldBeFalse)
   483  	})
   484  
   485  	Convey("Error message", t, func() {
   486  		chk := Checker{cfg: cfg, db: db, caller: authorizedID}
   487  		res := chk.CheckTaskPerm(ctx, TaskAuthInfo{
   488  			TaskID:    "65aba3a3e6b99310",
   489  			Realm:     "project:doesnt-matter",
   490  			BotID:     "hidden-bot",
   491  			Submitter: unknownID,
   492  		}, PermTasksCancel)
   493  		So(res.InternalError, ShouldBeFalse)
   494  		err := res.ToGrpcErr()
   495  		So(err, ShouldHaveGRPCStatus, codes.PermissionDenied)
   496  		So(err, ShouldErrLike, `the caller "user:authorized@example.com" doesn't have `+
   497  			`permission "swarming.tasks.cancel" for the task "65aba3a3e6b99310"`)
   498  	})
   499  }
   500  
   501  ////////////////////////////////////////////////////////////////////////////////
   502  
   503  // allPermissions returns all registered Swarming permissions.
   504  func allPermissions() []realms.Permission {
   505  	var perms []realms.Permission
   506  	for perm := range realms.RegisteredPermissions() {
   507  		if strings.HasPrefix(perm.String(), "swarming.") {
   508  			perms = append(perms, perm)
   509  		}
   510  	}
   511  	sort.Slice(perms, func(i, j int) bool { return perms[i].String() < perms[j].String() })
   512  	return perms
   513  }
   514  
   515  // assertSame fails if permissions sets have differences.
   516  func assertSame(got, want []realms.Permission) {
   517  	asSet := func(x []realms.Permission) []string {
   518  		s := stringset.New(len(x))
   519  		for _, p := range x {
   520  			s.Add(p.String())
   521  		}
   522  		return s.ToSortedSlice()
   523  	}
   524  	So(asSet(got), ShouldResemble, asSet(want))
   525  }
   526  
   527  // mockedConfig prepares a queryable config.
   528  func mockedConfig(settings *configpb.AuthSettings, pools map[string]string, bots map[string][]string) *cfg.Config {
   529  	// Note this logic is the same as in rpcs.MockConfigs, but we can't use it
   530  	// directly due to import cycles. It is a relatively small chunk of code, it's
   531  	// not worth extracting into a separate package. So just repeat it.
   532  	//
   533  	// It essentially uses the real config loading implementation, just on top of
   534  	// fake temporary datastore.
   535  
   536  	// Prepare minimal pools.cfg.
   537  	var poolpb []*configpb.Pool
   538  	for pool, realm := range pools {
   539  		poolpb = append(poolpb, &configpb.Pool{
   540  			Name:  []string{pool},
   541  			Realm: realm,
   542  		})
   543  	}
   544  	sort.Slice(poolpb, func(i, j int) bool { return poolpb[i].Name[0] < poolpb[j].Name[0] })
   545  
   546  	// Prepare minimal bots.cfg.
   547  	var botpb []*configpb.BotGroup
   548  	for botID, pools := range bots {
   549  		var dims []string
   550  		for _, pool := range pools {
   551  			dims = append(dims, "pool:"+pool)
   552  		}
   553  		botpb = append(botpb, &configpb.BotGroup{
   554  			BotId:      []string{botID},
   555  			Dimensions: dims,
   556  			Auth: []*configpb.BotAuth{ // required field
   557  				{
   558  					RequireLuciMachineToken: true,
   559  				},
   560  			},
   561  		})
   562  	}
   563  	sort.Slice(botpb, func(i, j int) bool { return botpb[i].BotId[0] < botpb[j].BotId[0] })
   564  
   565  	// Convert configs to raw proto text files.
   566  	files := make(cfgmem.Files)
   567  	putPb := func(path string, msg proto.Message) {
   568  		if msg != nil {
   569  			blob, err := prototext.Marshal(msg)
   570  			if err != nil {
   571  				panic(err)
   572  			}
   573  			files[path] = string(blob)
   574  		}
   575  	}
   576  	putPb("settings.cfg", &configpb.SettingsCfg{Auth: settings})
   577  	putPb("pools.cfg", &configpb.PoolsCfg{Pool: poolpb})
   578  	putPb("bots.cfg", &configpb.BotsCfg{
   579  		TrustedDimensions: []string{"pool"},
   580  		BotGroup:          botpb,
   581  	})
   582  
   583  	// Put new configs into a temporary fake datastore.
   584  	ctx := memory.Use(context.Background())
   585  	err := cfg.UpdateConfigs(cfgclient.Use(ctx, cfgmem.New(map[config.Set]cfgmem.Files{
   586  		"services/${appid}": files,
   587  	})))
   588  	if err != nil {
   589  		panic(err)
   590  	}
   591  
   592  	// Load them back in a queriable form.
   593  	p, err := cfg.NewProvider(ctx)
   594  	if err != nil {
   595  		panic(err)
   596  	}
   597  	return p.Config(ctx)
   598  }