go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/tokenserver/appengine/impl/serviceaccounts/config_validation_test.go (about)

     1  // Copyright 2020 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 serviceaccounts
    16  
    17  import (
    18  	"context"
    19  	"testing"
    20  
    21  	"google.golang.org/protobuf/encoding/prototext"
    22  
    23  	"go.chromium.org/luci/config/validation"
    24  
    25  	"go.chromium.org/luci/tokenserver/api/admin/v1"
    26  	"go.chromium.org/luci/tokenserver/appengine/impl/utils/policy"
    27  
    28  	. "github.com/smartystreets/goconvey/convey"
    29  	. "go.chromium.org/luci/common/testing/assertions"
    30  )
    31  
    32  func TestValidation(t *testing.T) {
    33  	t.Parallel()
    34  
    35  	cases := []struct {
    36  		Cfg    string
    37  		Errors []string
    38  	}{
    39  		{
    40  			// Non-trivial good config.
    41  			Cfg: `
    42  				mapping {
    43  					project: "proj1"
    44  					service_account: "exclusive-1@example.com"
    45  					service_account: "exclusive-2@example.com"
    46  				}
    47  
    48  				mapping {
    49  					project: "proj1"
    50  					project: "proj2"
    51  					service_account: "shared-1@example.com"
    52  					service_account: "shared-2@example.com"
    53  				}
    54  
    55  				mapping {
    56  					project: "proj3"
    57  					service_account: "exclusive-3@example.com"
    58  				}
    59  
    60  				mapping {
    61  					project: "@internal"
    62  					service_account: "exclusive-4@example.com"
    63  				}
    64  
    65  				use_project_scoped_account: "proj4"
    66  				use_project_scoped_account: "proj5"
    67  			`,
    68  		},
    69  
    70  		// Minimal config.
    71  		{
    72  			Cfg: ``,
    73  		},
    74  
    75  		// Empty list of project is not OK.
    76  		{
    77  			Cfg: `
    78  				mapping {
    79  					service_account: "sa1@example.com"
    80  				}
    81  			`,
    82  			Errors: []string{"at least one project must be given"},
    83  		},
    84  
    85  		// Empty list of accounts is not OK.
    86  		{
    87  			Cfg: `
    88  				mapping {
    89  					project: "proj"
    90  				}
    91  			`,
    92  			Errors: []string{"at least one service account must be given"},
    93  		},
    94  
    95  		// Bad project names.
    96  		{
    97  			Cfg: `
    98  				mapping {
    99  					project: ""
   100  					project: "  "
   101  					service_account: "sa@example.com"
   102  				}
   103  			`,
   104  			Errors: []string{
   105  				`bad project ""`,
   106  				`bad project "  "`,
   107  			},
   108  		},
   109  
   110  		// Bad service account names.
   111  		{
   112  			Cfg: `
   113  				mapping {
   114  					project: "proj"
   115  					service_account: ""
   116  					service_account: "not-email"
   117  				}
   118  			`,
   119  			Errors: []string{
   120  				`bad service_account ""`,
   121  				`bad service_account "not-email"`,
   122  			},
   123  		},
   124  
   125  		// Multiple mappings with the same account.
   126  		{
   127  			Cfg: `
   128  				mapping {
   129  					project: "proj1"
   130  					service_account: "sa@example.com"
   131  				}
   132  				mapping {
   133  					project: "proj2"
   134  					service_account: "sa@example.com"
   135  				}
   136  			`,
   137  			Errors: []string{
   138  				`service_account "sa@example.com" appears in more that one mapping`,
   139  			},
   140  		},
   141  
   142  		// Bad use_project_scoped_account.
   143  		{
   144  			Cfg: `
   145  				use_project_scoped_account: "  "
   146  			`,
   147  			Errors: []string{
   148  				`bad project in use_project_scoped_account #1 "  "`,
   149  			},
   150  		},
   151  
   152  		// Mapping for a project that uses scoped accounts.
   153  		{
   154  			Cfg: `
   155  				mapping {
   156  					project: "proj"
   157  					service_account: "sa@example.com"
   158  				}
   159  				use_project_scoped_account: "proj"
   160  			`,
   161  			Errors: []string{
   162  				`project "proj" is in use_project_scoped_account list, but also has mapping entries`,
   163  			},
   164  		},
   165  	}
   166  
   167  	Convey("Validation works", t, func(c C) {
   168  		for idx, cs := range cases {
   169  			c.Printf("Case #%d\n", idx)
   170  
   171  			cfg := &admin.ServiceAccountsProjectMapping{}
   172  			err := prototext.Unmarshal([]byte(cs.Cfg), cfg)
   173  			So(err, ShouldBeNil)
   174  
   175  			ctx := &validation.Context{Context: context.Background()}
   176  			validateConfigBundle(ctx, policy.ConfigBundle{configFileName: cfg})
   177  			verr := ctx.Finalize()
   178  
   179  			if len(cs.Errors) == 0 {
   180  				So(verr, ShouldBeNil)
   181  			} else {
   182  				verr := verr.(*validation.Error)
   183  				So(verr.Errors, ShouldHaveLength, len(cs.Errors))
   184  				for i, err := range verr.Errors {
   185  					So(err, ShouldErrLike, cs.Errors[i])
   186  				}
   187  			}
   188  		}
   189  	})
   190  }