go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/server/auth/authdb/dump/dump_test.go (about) 1 // Copyright 2019 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 dump 16 17 import ( 18 "context" 19 "crypto/sha512" 20 "fmt" 21 "net/http" 22 "net/http/httptest" 23 "testing" 24 25 "google.golang.org/protobuf/proto" 26 27 "go.chromium.org/luci/common/retry" 28 29 "go.chromium.org/luci/server/auth/authdb" 30 "go.chromium.org/luci/server/auth/authtest" 31 "go.chromium.org/luci/server/auth/service/protocol" 32 "go.chromium.org/luci/server/auth/signing/signingtest" 33 34 . "github.com/smartystreets/goconvey/convey" 35 . "go.chromium.org/luci/common/testing/assertions" 36 ) 37 38 func TestFetcher(t *testing.T) { 39 t.Parallel() 40 41 signer := signingtest.NewSigner(nil) 42 43 Convey("With mocks", t, func(c C) { 44 serverAllowAccess := true // is caller allowed to access authdb? 45 serverHasAccessNow := false // does caller have access right now? 46 serverDumpPath := "bucket/prefix" // where server dumps AuthDB 47 serverSignerID := "auth-service-id" // ID used to sign blob by the server 48 serverSignature := []byte(nil) // if non-nil, mocked signature 49 50 serverLatestRev := int64(1234) // revision of the latest dump 51 serverLatestVal := "latest" // payload in the latest dump 52 53 ctx := authtest.MockAuthConfig(context.Background()) 54 55 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 56 switch r.URL.Path { 57 case "/auth_service/api/v1/authdb/subscription/authorization": 58 if !serverAllowAccess { 59 w.WriteHeader(403) 60 } else { 61 serverHasAccessNow = true 62 w.Write([]byte(fmt.Sprintf(`{"gs":{"auth_db_gs_path":%q}}`, serverDumpPath))) 63 } 64 case "/bucket/prefix/latest.json": 65 if !serverHasAccessNow { 66 w.WriteHeader(403) 67 } else { 68 w.Write([]byte(fmt.Sprintf(`{"auth_db_rev": "%d"}`, serverLatestRev))) 69 } 70 case "/bucket/prefix/latest.db": 71 if !serverHasAccessNow { 72 w.WriteHeader(403) 73 } else { 74 w.Write(genSignedAuthDB(ctx, signer, 75 serverSignerID, serverSignature, serverLatestRev, serverLatestVal)) 76 } 77 default: 78 c.So(r.URL, ShouldEqual, "") 79 } 80 })) 81 defer ts.Close() 82 83 signingCerts, err := signer.Certificates(ctx) 84 So(err, ShouldBeNil) 85 86 f := Fetcher{ 87 StorageDumpPath: "bucket/prefix", 88 AuthServiceURL: ts.URL, 89 AuthServiceAccount: serverSignerID, 90 OAuthScopes: []string{"scope1", "scope2"}, 91 92 testRetryPolicy: func() retry.Iterator { return &retry.Limited{Retries: 0} }, 93 testStorageURL: ts.URL, 94 testStorageClient: http.DefaultClient, 95 testSigningCerts: signingCerts, 96 } 97 98 Convey("Fetching works", func() { 99 db, err := f.FetchAuthDB(ctx, nil) 100 So(err, ShouldBeNil) 101 assertAuthDB(db, serverLatestRev, serverLatestVal) 102 103 Convey("Skips updating if nothing has changed", func() { 104 fetched, err := f.FetchAuthDB(ctx, db) 105 So(err, ShouldBeNil) 106 So(fetched == db, ShouldBeTrue) // the exact same object 107 }) 108 109 Convey("Updates if the revision goes up", func() { 110 serverLatestRev++ 111 serverLatestVal = "newer" 112 113 fetched, err := f.FetchAuthDB(ctx, db) 114 So(err, ShouldBeNil) 115 assertAuthDB(fetched, serverLatestRev, serverLatestVal) 116 }) 117 118 Convey("Refuses to update if the revision goes down", func() { 119 serverLatestRev-- 120 serverLatestVal = "older" 121 122 fetched, err := f.FetchAuthDB(ctx, db) 123 So(err, ShouldBeNil) 124 So(fetched == db, ShouldBeTrue) // the exact same object 125 }) 126 }) 127 128 Convey("Not authorized", func() { 129 serverAllowAccess = false 130 _, err := f.FetchAuthDB(ctx, nil) 131 So(err, ShouldErrLike, "HTTP code (403)") 132 }) 133 134 Convey("Wrong storage path", func() { 135 serverDumpPath = "something/else" 136 _, err := f.FetchAuthDB(ctx, nil) 137 So(err, ShouldErrLike, "wrong configuration") 138 }) 139 140 Convey("Unexpected signer", func() { 141 serverSignerID = "someone-else" 142 _, err := f.FetchAuthDB(ctx, nil) 143 So(err, ShouldErrLike, "the snapshot is signed by") 144 }) 145 146 Convey("Bad signature", func() { 147 serverSignature = []byte("bad signature") 148 _, err := f.FetchAuthDB(ctx, nil) 149 So(err, ShouldErrLike, "failed to verify that AuthDB was signed") 150 }) 151 }) 152 } 153 154 // genSignedAuthDB generates and signs auth DB blob. 155 func genSignedAuthDB(ctx context.Context, signer *signingtest.Signer, signerID string, sig []byte, rev int64, data string) []byte { 156 authDB, err := proto.Marshal(&protocol.ReplicationPushRequest{ 157 Revision: &protocol.AuthDBRevision{AuthDbRev: rev}, 158 // We abuse TokenServerUrl to pass some payload checked by assertAuthDB. 159 AuthDb: &protocol.AuthDB{TokenServerUrl: data}, 160 }) 161 if err != nil { 162 panic(err) 163 } 164 165 hash := sha512.Sum512(authDB) 166 keyID, realSig, err := signer.SignBytes(ctx, hash[:]) 167 if err != nil { 168 panic(err) 169 } 170 if sig == nil { 171 sig = realSig 172 } 173 174 blob, err := proto.Marshal(&protocol.SignedAuthDB{ 175 AuthDbBlob: authDB, 176 SignerId: signerID, 177 SigningKeyId: keyID, 178 Signature: sig, 179 }) 180 if err != nil { 181 panic(err) 182 } 183 return blob 184 } 185 186 // assertAuthDB verifies 'db' was constructed from output of genSignedAuthDB. 187 func assertAuthDB(db *authdb.SnapshotDB, rev int64, data string) { 188 So(db.Rev, ShouldEqual, rev) 189 d, _ := db.GetTokenServiceURL(nil) 190 So(d, ShouldEqual, data) 191 }