| OLD | NEW |
| (Empty) | |
| 1 // Copyright 2016 The LUCI Authors. All rights reserved. |
| 2 // Use of this source code is governed under the Apache License, Version 2.0 |
| 3 // that can be found in the LICENSE file. |
| 4 |
| 5 package delegation |
| 6 |
| 7 import ( |
| 8 "fmt" |
| 9 "net/url" |
| 10 "testing" |
| 11 |
| 12 "golang.org/x/net/context" |
| 13 |
| 14 "github.com/luci/luci-go/server/auth" |
| 15 "github.com/luci/luci-go/server/auth/authtest" |
| 16 "github.com/luci/luci-go/server/auth/identity" |
| 17 admin "github.com/luci/luci-go/tokenserver/api/admin/v1" |
| 18 minter "github.com/luci/luci-go/tokenserver/api/minter/v1" |
| 19 |
| 20 . "github.com/luci/luci-go/common/testing/assertions" |
| 21 . "github.com/smartystreets/goconvey/convey" |
| 22 ) |
| 23 |
| 24 func mockedFetchLUCIServiceIdentity(c context.Context, u string) (identity.Ident
ity, error) { |
| 25 l, err := url.Parse(u) |
| 26 if err != nil { |
| 27 return "", err |
| 28 } |
| 29 if l.Scheme != "https" { |
| 30 return "", fmt.Errorf("wrong scheme") |
| 31 } |
| 32 if l.Host == "crash" { |
| 33 return "", fmt.Errorf("boom") |
| 34 } |
| 35 return identity.MakeIdentity("service:" + l.Host) |
| 36 } |
| 37 |
| 38 func init() { |
| 39 fetchLUCIServiceIdentity = mockedFetchLUCIServiceIdentity |
| 40 } |
| 41 |
| 42 func TestBuildRulesQuery(t *testing.T) { |
| 43 ctx := context.Background() |
| 44 |
| 45 Convey("Happy path", t, func() { |
| 46 q, err := buildRulesQuery(ctx, &minter.MintDelegationTokenReques
t{ |
| 47 DelegatedIdentity: "user:delegated@example.com", |
| 48 Audience: []string{"group:A", "group:B", "user:
c@example.com"}, |
| 49 Services: []string{"service:A", "*"}, |
| 50 }, "user:requestor@example.com") |
| 51 So(err, ShouldBeNil) |
| 52 So(q, ShouldNotBeNil) |
| 53 |
| 54 So(q.Requestor, ShouldEqual, "user:requestor@example.com") |
| 55 So(q.Delegatee, ShouldEqual, "user:delegated@example.com") |
| 56 So(q.Audience.ToStrings(), ShouldResemble, []string{"group:A", "
group:B", "user:c@example.com"}) |
| 57 So(q.Services.ToStrings(), ShouldResemble, []string{"*"}) |
| 58 }) |
| 59 |
| 60 Convey("REQUESTOR usage works", t, func() { |
| 61 q, err := buildRulesQuery(ctx, &minter.MintDelegationTokenReques
t{ |
| 62 DelegatedIdentity: "REQUESTOR", |
| 63 Audience: []string{"group:A", "group:B", "REQUE
STOR"}, |
| 64 Services: []string{"*"}, |
| 65 }, "user:requestor@example.com") |
| 66 So(err, ShouldBeNil) |
| 67 So(q, ShouldNotBeNil) |
| 68 |
| 69 So(q.Requestor, ShouldEqual, "user:requestor@example.com") |
| 70 So(q.Delegatee, ShouldEqual, "user:requestor@example.com") |
| 71 So(q.Audience.ToStrings(), ShouldResemble, []string{"group:A", "
group:B", "user:requestor@example.com"}) |
| 72 }) |
| 73 |
| 74 Convey("bad 'delegated_identity'", t, func() { |
| 75 _, err := buildRulesQuery(ctx, &minter.MintDelegationTokenReques
t{ |
| 76 Audience: []string{"REQUESTOR"}, |
| 77 Services: []string{"*"}, |
| 78 }, "user:requestor@example.com") |
| 79 So(err, ShouldErrLike, `'delegated_identity' is required`) |
| 80 |
| 81 _, err = buildRulesQuery(ctx, &minter.MintDelegationTokenRequest
{ |
| 82 DelegatedIdentity: "junk", |
| 83 Audience: []string{"REQUESTOR"}, |
| 84 Services: []string{"*"}, |
| 85 }, "user:requestor@example.com") |
| 86 So(err, ShouldErrLike, `bad 'delegated_identity' - auth: bad ide
ntity string "junk"`) |
| 87 }) |
| 88 |
| 89 Convey("bad 'audience'", t, func() { |
| 90 _, err := buildRulesQuery(ctx, &minter.MintDelegationTokenReques
t{ |
| 91 DelegatedIdentity: "REQUESTOR", |
| 92 Services: []string{"*"}, |
| 93 }, "user:requestor@example.com") |
| 94 So(err, ShouldErrLike, `'audience' is required`) |
| 95 |
| 96 _, err = buildRulesQuery(ctx, &minter.MintDelegationTokenRequest
{ |
| 97 DelegatedIdentity: "REQUESTOR", |
| 98 Audience: []string{"REQUESTOR", "junk"}, |
| 99 Services: []string{"*"}, |
| 100 }, "user:requestor@example.com") |
| 101 So(err, ShouldErrLike, `bad 'audience' - auth: bad identity stri
ng "junk"`) |
| 102 }) |
| 103 |
| 104 Convey("bad 'services'", t, func() { |
| 105 _, err := buildRulesQuery(ctx, &minter.MintDelegationTokenReques
t{ |
| 106 DelegatedIdentity: "REQUESTOR", |
| 107 Audience: []string{"REQUESTOR"}, |
| 108 }, "user:requestor@example.com") |
| 109 So(err, ShouldErrLike, `'services' is required`) |
| 110 |
| 111 _, err = buildRulesQuery(ctx, &minter.MintDelegationTokenRequest
{ |
| 112 DelegatedIdentity: "REQUESTOR", |
| 113 Audience: []string{"REQUESTOR"}, |
| 114 Services: []string{"junk"}, |
| 115 }, "user:requestor@example.com") |
| 116 So(err, ShouldErrLike, `bad 'services' - auth: bad identity stri
ng "junk"`) |
| 117 |
| 118 _, err = buildRulesQuery(ctx, &minter.MintDelegationTokenRequest
{ |
| 119 DelegatedIdentity: "REQUESTOR", |
| 120 Audience: []string{"REQUESTOR"}, |
| 121 Services: []string{"user:abc@example.com"}, |
| 122 }, "user:requestor@example.com") |
| 123 So(err, ShouldErrLike, `bad 'services' - "user:abc@example.com"
is not a service ID`) |
| 124 |
| 125 _, err = buildRulesQuery(ctx, &minter.MintDelegationTokenRequest
{ |
| 126 DelegatedIdentity: "REQUESTOR", |
| 127 Audience: []string{"REQUESTOR"}, |
| 128 Services: []string{"group:abc"}, |
| 129 }, "user:requestor@example.com") |
| 130 So(err, ShouldErrLike, `bad 'services' - can't specify groups`) |
| 131 }) |
| 132 |
| 133 Convey("resolves https:// service refs", t, func() { |
| 134 q, err := buildRulesQuery(ctx, &minter.MintDelegationTokenReques
t{ |
| 135 DelegatedIdentity: "user:delegated@example.com", |
| 136 Audience: []string{"*"}, |
| 137 Services: []string{ |
| 138 "service:A", |
| 139 "service:B", |
| 140 "https://C", |
| 141 "https://B", |
| 142 "https://A", |
| 143 }, |
| 144 }, "user:requestor@example.com") |
| 145 So(err, ShouldBeNil) |
| 146 So(q, ShouldNotBeNil) |
| 147 |
| 148 So(q.Services.ToStrings(), ShouldResemble, []string{ |
| 149 "service:A", |
| 150 "service:B", |
| 151 "service:C", |
| 152 }) |
| 153 }) |
| 154 |
| 155 Convey("handles errors when resolving https:// service refs", t, func()
{ |
| 156 _, err := buildRulesQuery(ctx, &minter.MintDelegationTokenReques
t{ |
| 157 DelegatedIdentity: "user:delegated@example.com", |
| 158 Audience: []string{"*"}, |
| 159 Services: []string{ |
| 160 "https://A", |
| 161 "https://B", |
| 162 "https://crash", |
| 163 }, |
| 164 }, "user:requestor@example.com") |
| 165 So(err, ShouldErrLike, `could not resolve "https://crash" to ser
vice ID - boom`) |
| 166 }) |
| 167 } |
| 168 |
| 169 func TestMintDelegationToken(t *testing.T) { |
| 170 Convey("with mocked config and state", t, func() { |
| 171 cfg, err := loadConfig(` |
| 172 rules { |
| 173 name: "requstor for itself" |
| 174 requestor: "user:requestor@example.com" |
| 175 target_service: "*" |
| 176 allowed_to_impersonate: "REQUESTOR" |
| 177 allowed_audience: "REQUESTOR" |
| 178 max_validity_duration: 3600 |
| 179 } |
| 180 `) |
| 181 So(err, ShouldBeNil) |
| 182 |
| 183 mintMock := func(context.Context, *minter.MintDelegationTokenReq
uest, |
| 184 *RulesQuery, *admin.DelegationRule) (*minter.MintDelegat
ionTokenResponse, error) { |
| 185 return &minter.MintDelegationTokenResponse{Token: "valid
_token"}, nil |
| 186 } |
| 187 |
| 188 rpc := MintDelegationTokenRPC{ |
| 189 ConfigLoader: func(context.Context) (*DelegationConfig,
error) { return cfg, nil }, |
| 190 mintMock: mintMock, |
| 191 } |
| 192 |
| 193 Convey("Happy path", func() { |
| 194 ctx := auth.WithState(context.Background(), &authtest.Fa
keState{ |
| 195 Identity: "user:requestor@example.com", |
| 196 }) |
| 197 resp, err := rpc.MintDelegationToken(ctx, &minter.MintDe
legationTokenRequest{ |
| 198 DelegatedIdentity: "REQUESTOR", |
| 199 Audience: []string{"REQUESTOR"}, |
| 200 Services: []string{"*"}, |
| 201 }) |
| 202 So(err, ShouldBeNil) |
| 203 So(resp.Token, ShouldEqual, "valid_token") |
| 204 }) |
| 205 |
| 206 Convey("Using delegated identity for auth is forbidden", func()
{ |
| 207 ctx := auth.WithState(context.Background(), &authtest.Fa
keState{ |
| 208 Identity: "user:requestor@example.co
m", |
| 209 PeerIdentityOverride: "user:impersonator@example
.com", |
| 210 }) |
| 211 _, err := rpc.MintDelegationToken(ctx, &minter.MintDeleg
ationTokenRequest{ |
| 212 DelegatedIdentity: "REQUESTOR", |
| 213 Audience: []string{"REQUESTOR"}, |
| 214 Services: []string{"*"}, |
| 215 }) |
| 216 So(err, ShouldErrLike, `code = 7 desc = delegation is fo
rbidden for this API call`) |
| 217 }) |
| 218 |
| 219 Convey("Anonymous calls are forbidden", func() { |
| 220 ctx := auth.WithState(context.Background(), &authtest.Fa
keState{ |
| 221 Identity: "anonymous:anonymous", |
| 222 }) |
| 223 _, err := rpc.MintDelegationToken(ctx, &minter.MintDeleg
ationTokenRequest{ |
| 224 DelegatedIdentity: "REQUESTOR", |
| 225 Audience: []string{"REQUESTOR"}, |
| 226 Services: []string{"*"}, |
| 227 }) |
| 228 So(err, ShouldErrLike, `code = 16 desc = authentication
required`) |
| 229 }) |
| 230 |
| 231 Convey("Unauthorized requestor", func() { |
| 232 ctx := auth.WithState(context.Background(), &authtest.Fa
keState{ |
| 233 Identity: "user:unknown@example.cim", |
| 234 }) |
| 235 _, err := rpc.MintDelegationToken(ctx, &minter.MintDeleg
ationTokenRequest{ |
| 236 DelegatedIdentity: "REQUESTOR", |
| 237 Audience: []string{"REQUESTOR"}, |
| 238 Services: []string{"*"}, |
| 239 }) |
| 240 So(err, ShouldErrLike, `code = 7 desc = not authorized`) |
| 241 }) |
| 242 |
| 243 Convey("Negative validity duration", func() { |
| 244 ctx := auth.WithState(context.Background(), &authtest.Fa
keState{ |
| 245 Identity: "user:requestor@example.com", |
| 246 }) |
| 247 _, err := rpc.MintDelegationToken(ctx, &minter.MintDeleg
ationTokenRequest{ |
| 248 DelegatedIdentity: "REQUESTOR", |
| 249 Audience: []string{"REQUESTOR"}, |
| 250 Services: []string{"*"}, |
| 251 ValidityDuration: -1, |
| 252 }) |
| 253 So(err, ShouldErrLike, `code = 3 desc = bad request - in
valid 'validity_duration' (-1)`) |
| 254 }) |
| 255 |
| 256 Convey("Malformed request", func() { |
| 257 ctx := auth.WithState(context.Background(), &authtest.Fa
keState{ |
| 258 Identity: "user:requestor@example.com", |
| 259 }) |
| 260 _, err := rpc.MintDelegationToken(ctx, &minter.MintDeleg
ationTokenRequest{ |
| 261 DelegatedIdentity: "REQUESTOR", |
| 262 Audience: []string{"junk"}, |
| 263 Services: []string{"*"}, |
| 264 }) |
| 265 So(err, ShouldErrLike, `code = 3 desc = bad request - ba
d 'audience' - auth: bad identity string "junk"`) |
| 266 }) |
| 267 |
| 268 Convey("No matching rules", func() { |
| 269 ctx := auth.WithState(context.Background(), &authtest.Fa
keState{ |
| 270 Identity: "user:requestor@example.com", |
| 271 }) |
| 272 _, err := rpc.MintDelegationToken(ctx, &minter.MintDeleg
ationTokenRequest{ |
| 273 DelegatedIdentity: "REQUESTOR", |
| 274 Audience: []string{"user:someone-else@e
xample.com"}, |
| 275 Services: []string{"*"}, |
| 276 }) |
| 277 So(err, ShouldErrLike, `code = 7 desc = forbidden - no m
atching delegation rules in the config`) |
| 278 }) |
| 279 |
| 280 Convey("Forbidden validity duration", func() { |
| 281 ctx := auth.WithState(context.Background(), &authtest.Fa
keState{ |
| 282 Identity: "user:requestor@example.com", |
| 283 }) |
| 284 _, err := rpc.MintDelegationToken(ctx, &minter.MintDeleg
ationTokenRequest{ |
| 285 DelegatedIdentity: "REQUESTOR", |
| 286 Audience: []string{"REQUESTOR"}, |
| 287 Services: []string{"*"}, |
| 288 ValidityDuration: 3601, |
| 289 }) |
| 290 So(err, ShouldErrLike, |
| 291 `rpc error: code = 7 desc = forbidden - the requ
ested validity duration (3601 sec) exceeds the maximum allowed one (3600 sec)`) |
| 292 }) |
| 293 |
| 294 }) |
| 295 } |
| OLD | NEW |