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  }