go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/server/auth/delegation/checker_test.go (about) 1 // Copyright 2016 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 delegation 16 17 import ( 18 "context" 19 "encoding/base64" 20 "os" 21 "strings" 22 "testing" 23 "time" 24 25 "google.golang.org/protobuf/proto" 26 27 "go.chromium.org/luci/auth/identity" 28 "go.chromium.org/luci/common/clock" 29 "go.chromium.org/luci/common/clock/testclock" 30 "go.chromium.org/luci/common/logging" 31 "go.chromium.org/luci/common/logging/memlogger" 32 33 "go.chromium.org/luci/server/auth/delegation/messages" 34 "go.chromium.org/luci/server/auth/signing" 35 "go.chromium.org/luci/server/auth/signing/signingtest" 36 37 . "github.com/smartystreets/goconvey/convey" 38 ) 39 40 func TestCheckToken(t *testing.T) { 41 c := memlogger.Use(context.Background()) 42 c, _ = testclock.UseTime(c, testclock.TestRecentTimeUTC) 43 minter := newFakeTokenMinter() 44 45 defer func() { 46 if t.Failed() { 47 logging.Get(c).(*memlogger.MemLogger).Dump(os.Stderr) 48 } 49 }() 50 51 Convey("Basic use case", t, func() { 52 tok := minter.mintToken(c, subtoken(c, "user:from@example.com", "user:to@example.com")) 53 ident, err := CheckToken(c, CheckTokenParams{ 54 Token: tok, 55 PeerID: "user:to@example.com", 56 CertificatesProvider: minter, 57 GroupsChecker: &fakeGroups{}, 58 OwnServiceIdentity: "service:service-id", 59 }) 60 So(err, ShouldBeNil) 61 So(ident, ShouldEqual, identity.Identity("user:from@example.com")) 62 }) 63 64 Convey("Basic use case with group check", t, func() { 65 tok := minter.mintToken(c, subtoken(c, "user:from@example.com", "group:token-users")) 66 67 groups := &fakeGroups{ 68 groups: map[string]string{ 69 "token-users": "user:to@example.com", 70 }, 71 } 72 73 // Pass. 74 ident, err := CheckToken(c, CheckTokenParams{ 75 Token: tok, 76 PeerID: "user:to@example.com", 77 CertificatesProvider: minter, 78 GroupsChecker: groups, 79 OwnServiceIdentity: "service:service-id", 80 }) 81 So(err, ShouldBeNil) 82 So(ident, ShouldEqual, identity.Identity("user:from@example.com")) 83 84 // Fail. 85 _, err = CheckToken(c, CheckTokenParams{ 86 Token: tok, 87 PeerID: "user:NOT-to@example.com", 88 CertificatesProvider: minter, 89 GroupsChecker: groups, 90 OwnServiceIdentity: "service:service-id", 91 }) 92 So(err, ShouldEqual, ErrForbiddenDelegationToken) 93 }) 94 95 Convey("Not base64", t, func() { 96 _, err := CheckToken(c, CheckTokenParams{ 97 Token: "(^*#%^&#%", 98 PeerID: "user:to@example.com", 99 CertificatesProvider: minter, 100 GroupsChecker: &fakeGroups{}, 101 OwnServiceIdentity: "service:service-id", 102 }) 103 So(err, ShouldEqual, ErrMalformedDelegationToken) 104 }) 105 106 Convey("Huge token is skipped", t, func() { 107 _, err := CheckToken(c, CheckTokenParams{ 108 Token: strings.Repeat("aaaa", 10000), 109 PeerID: "user:to@example.com", 110 CertificatesProvider: minter, 111 GroupsChecker: &fakeGroups{}, 112 OwnServiceIdentity: "service:service-id", 113 }) 114 So(err, ShouldEqual, ErrMalformedDelegationToken) 115 }) 116 117 Convey("Untrusted signer", t, func() { 118 tok := minter.mintToken(c, subtoken(c, "user:from@example.com", "user:to@example.com")) 119 minter.signerID = "service:nah-i-renamed-myself" 120 _, err := CheckToken(c, CheckTokenParams{ 121 Token: tok, 122 PeerID: "user:to@example.com", 123 CertificatesProvider: minter, 124 GroupsChecker: &fakeGroups{}, 125 OwnServiceIdentity: "service:service-id", 126 }) 127 So(err, ShouldEqual, ErrUnsignedDelegationToken) 128 }) 129 130 Convey("Bad signature", t, func() { 131 tok := minter.mintToken(c, subtoken(c, "user:from@example.com", "user:to@example.com")) 132 // An offset in serialized token that points to Subtoken field. Replace one 133 // byte there to "break" the signature. 134 sigOffset := len(tok) - 10 135 So(tok[sigOffset], ShouldNotEqual, 'A') 136 _, err := CheckToken(c, CheckTokenParams{ 137 Token: tok[:sigOffset] + "A" + tok[sigOffset+1:], 138 PeerID: "user:to@example.com", 139 CertificatesProvider: minter, 140 GroupsChecker: &fakeGroups{}, 141 OwnServiceIdentity: "service:service-id", 142 }) 143 So(err, ShouldEqual, ErrUnsignedDelegationToken) 144 }) 145 146 Convey("Expired token", t, func() { 147 tok := minter.mintToken(c, subtoken(c, "user:from@example.com", "user:to@example.com")) 148 149 clock.Get(c).(testclock.TestClock).Add(2 * time.Hour) 150 151 _, err := CheckToken(c, CheckTokenParams{ 152 Token: tok, 153 PeerID: "user:to@example.com", 154 CertificatesProvider: minter, 155 GroupsChecker: &fakeGroups{}, 156 OwnServiceIdentity: "service:service-id", 157 }) 158 So(err, ShouldEqual, ErrForbiddenDelegationToken) 159 }) 160 161 Convey("Wrong target service", t, func() { 162 tok := minter.mintToken(c, subtoken(c, "user:from@example.com", "user:to@example.com")) 163 _, err := CheckToken(c, CheckTokenParams{ 164 Token: tok, 165 PeerID: "user:to@example.com", 166 CertificatesProvider: minter, 167 GroupsChecker: &fakeGroups{}, 168 OwnServiceIdentity: "service:NOT-a-service-id", 169 }) 170 So(err, ShouldEqual, ErrForbiddenDelegationToken) 171 }) 172 173 Convey("Wrong audience", t, func() { 174 tok := minter.mintToken(c, subtoken(c, "user:from@example.com", "user:to@example.com")) 175 _, err := CheckToken(c, CheckTokenParams{ 176 Token: tok, 177 PeerID: "user:NOT-to@example.com", 178 CertificatesProvider: minter, 179 GroupsChecker: &fakeGroups{}, 180 OwnServiceIdentity: "service:service-id", 181 }) 182 So(err, ShouldEqual, ErrForbiddenDelegationToken) 183 }) 184 185 } 186 187 // subtoken returns messages.Subtoken with some fields filled in. 188 func subtoken(ctx context.Context, delegatedID, audience string) *messages.Subtoken { 189 return &messages.Subtoken{ 190 Kind: messages.Subtoken_BEARER_DELEGATION_TOKEN, 191 DelegatedIdentity: delegatedID, 192 CreationTime: clock.Now(ctx).Unix() - 300, 193 ValidityDuration: 3600, 194 Audience: []string{audience}, 195 Services: []string{"service:service-id"}, 196 } 197 } 198 199 // fakeTokenMinter knows how to generate tokens. 200 // 201 // It also implements CertificatesProvider protocol that is used when validating 202 // the tokens. 203 type fakeTokenMinter struct { 204 signer signing.Signer 205 signerID string 206 } 207 208 func newFakeTokenMinter() *fakeTokenMinter { 209 return &fakeTokenMinter{ 210 signer: signingtest.NewSigner(nil), 211 signerID: "service:fake-signer", 212 } 213 } 214 215 func (f *fakeTokenMinter) GetCertificates(ctx context.Context, id identity.Identity) (*signing.PublicCertificates, error) { 216 if string(id) != f.signerID { 217 return nil, nil 218 } 219 return f.signer.Certificates(ctx) 220 } 221 222 func (f *fakeTokenMinter) mintToken(ctx context.Context, subtoken *messages.Subtoken) string { 223 blob, err := proto.Marshal(subtoken) 224 if err != nil { 225 panic(err) 226 } 227 keyID, sig, err := f.signer.SignBytes(ctx, blob) 228 if err != nil { 229 panic(err) 230 } 231 tok, err := proto.Marshal(&messages.DelegationToken{ 232 SerializedSubtoken: blob, 233 SignerId: f.signerID, 234 SigningKeyId: keyID, 235 Pkcs1Sha256Sig: sig, 236 }) 237 if err != nil { 238 panic(err) 239 } 240 return base64.RawURLEncoding.EncodeToString(tok) 241 } 242 243 // fakeGroups implements GroupsChecker. 244 type fakeGroups struct { 245 groups map[string]string // if nil, IsMember always returns false 246 } 247 248 func (f *fakeGroups) IsMember(ctx context.Context, id identity.Identity, groups []string) (bool, error) { 249 for _, group := range groups { 250 if f.groups[group] == string(id) { 251 return true, nil 252 } 253 } 254 return false, nil 255 }