go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/auth/client/cmd/authdb-dump/main.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 // Command authdb-dump can dump AuthDB proto served by an Auth Service. 16 // 17 // This is to aid in developing Realms API and debugging issues. Not intended to 18 // be used in any production setting. 19 package main 20 21 import ( 22 "bytes" 23 "compress/zlib" 24 "context" 25 "encoding/base64" 26 "encoding/json" 27 "flag" 28 "fmt" 29 "io" 30 "net/http" 31 "os" 32 "time" 33 34 "github.com/dustin/go-humanize" 35 "github.com/golang/protobuf/proto" 36 37 "go.chromium.org/luci/auth" 38 "go.chromium.org/luci/auth/client/authcli" 39 "go.chromium.org/luci/common/errors" 40 "go.chromium.org/luci/common/logging" 41 "go.chromium.org/luci/common/logging/gologger" 42 "go.chromium.org/luci/hardcoded/chromeinfra" 43 "go.chromium.org/luci/server/auth/service/protocol" 44 ) 45 46 var ( 47 authServiceURL = flag.String("auth-service-url", "https://chrome-infra-auth.appspot.com", 48 "https:// URL of a Auth Service to fetch realms from") 49 outputFile = flag.String("output-proto-file", "", 50 "If set, write the protocol.AuthDB to this file using wirepb encoding instead of dumping it as text to stdout") 51 ) 52 53 func main() { 54 ctx := context.Background() 55 ctx = gologger.StdConfig.Use(ctx) 56 if err := run(ctx); err != nil { 57 fmt.Fprintf(os.Stderr, "%s\n", err) 58 os.Exit(1) 59 } 60 } 61 62 func run(ctx context.Context) error { 63 authFlags := authcli.Flags{} 64 authFlags.Register(flag.CommandLine, chromeinfra.DefaultAuthOptions()) 65 66 flag.Parse() 67 68 opts, err := authFlags.Options() 69 if err != nil { 70 return err 71 } 72 authenticator := auth.NewAuthenticator(ctx, auth.SilentLogin, opts) 73 client, err := authenticator.Client() 74 if err != nil { 75 return err 76 } 77 78 authDB, err := fetchAuthDB(ctx, client, *authServiceURL) 79 if err != nil { 80 return err 81 } 82 83 if *outputFile != "" { 84 blob, err := proto.Marshal(authDB) 85 if err != nil { 86 return err 87 } 88 return os.WriteFile(*outputFile, blob, 0600) 89 } 90 91 logging.Infof(ctx, "AuthDB proto:") 92 fmt.Printf("%s", proto.MarshalTextString(authDB)) 93 return nil 94 } 95 96 func fetchAuthDB(ctx context.Context, client *http.Client, authServiceURL string) (*protocol.AuthDB, error) { 97 req, err := http.NewRequest("GET", authServiceURL+"/auth_service/api/v1/authdb/revisions/latest", nil) 98 if err != nil { 99 return nil, errors.Annotate(err, "failed to prepare the request").Err() 100 } 101 102 // Grab JSON with base64-encoded deflated AuthDB snapshot. 103 logging.Infof(ctx, "Sending the request to %s...", authServiceURL) 104 resp, err := client.Do(req.WithContext(ctx)) 105 if err != nil { 106 return nil, errors.Annotate(err, "failed to send the request to the auth service").Err() 107 } 108 body, err := io.ReadAll(resp.Body) 109 resp.Body.Close() 110 if err != nil { 111 return nil, errors.Annotate(err, "failed to read the response from the auth service").Err() 112 } 113 if resp.StatusCode != 200 { 114 return nil, errors.Reason("unexpected response with code %d from the auth service: %s", resp.StatusCode, body).Err() 115 } 116 117 // Extract deflated ReplicationPushRequest from it. 118 var out struct { 119 Snapshot struct { 120 Rev int64 `json:"auth_db_rev"` 121 SHA256 string `json:"sha256"` 122 Created int64 `json:"created_ts"` 123 DeflatedBody string `json:"deflated_body"` 124 } `json:"snapshot"` 125 } 126 if err := json.Unmarshal(body, &out); err != nil { 127 return nil, errors.Annotate(err, "failed to JSON unmarshal the response").Err() 128 } 129 deflated, err := base64.StdEncoding.DecodeString(out.Snapshot.DeflatedBody) 130 if err != nil { 131 return nil, errors.Annotate(err, "failed to base64-decode").Err() 132 } 133 134 // Inflate it. 135 reader, err := zlib.NewReader(bytes.NewReader(deflated)) 136 if err != nil { 137 return nil, errors.Annotate(err, "failed to start inflating").Err() 138 } 139 inflated := bytes.Buffer{} 140 if _, err := io.Copy(&inflated, reader); err != nil { 141 return nil, errors.Annotate(err, "failed to inflate").Err() 142 } 143 if err := reader.Close(); err != nil { 144 return nil, errors.Annotate(err, "failed to inflate").Err() 145 } 146 147 // Unmarshal the actual proto message contained there. 148 msg := protocol.ReplicationPushRequest{} 149 if err := proto.Unmarshal(inflated.Bytes(), &msg); err != nil { 150 return nil, errors.Annotate(err, "failed to deserialize AuthDB proto").Err() 151 } 152 153 // Log some stats. 154 logging.Infof(ctx, "AuthDB rev %d, created %s by the auth service v%s", 155 out.Snapshot.Rev, humanize.Time(time.Unix(0, out.Snapshot.Created*1000)), 156 msg.AuthCodeVersion) 157 logging.Infof(ctx, "Raw response size: %d bytes", len(body)) 158 logging.Infof(ctx, "Deflated size: %d bytes", len(deflated)) 159 logging.Infof(ctx, "Inflated size: %d bytes", inflated.Len()) 160 161 return msg.AuthDb, nil 162 }