From 85e6a2171727789715e3e6cc3e7d492d7b1b8f0d Mon Sep 17 00:00:00 2001 From: WeebDataHoarder <57538841+WeebDataHoarder@users.noreply.github.com> Date: Fri, 10 Jun 2022 11:22:11 +0200 Subject: [PATCH] tls/http statistics --- OrbitalBeat.go | 148 +++++++++++++++++++++++++++++++++++++++++++------ go.mod | 9 ++- go.sum | 18 +++--- 3 files changed, 142 insertions(+), 33 deletions(-) diff --git a/OrbitalBeat.go b/OrbitalBeat.go index cfd2ea8..caede88 100644 --- a/OrbitalBeat.go +++ b/OrbitalBeat.go @@ -2,6 +2,7 @@ package main import ( "bytes" + "crypto/tls" "database/sql" "encoding/base64" "encoding/hex" @@ -50,7 +51,14 @@ type statistics struct { Served servedStatistics `json:"served"` ContentCache contentCacheStatistics `json:"content_cache"` SignatureCache signatureCacheStatistics `json:"signature_cache"` - HttpCode httpCodeStatistics `json:"http_code"` + Http struct { + Code httpCodeStatistics `json:"code"` + Protocol httpProtocolStatistics `json:"protocol"` + } + Tls struct { + Version tlsVersionStatistics `json:"version"` + Cipher tlsCipherStatistics `json:"cipher"` + } } type servedStatistics struct { @@ -72,6 +80,7 @@ type signatureCacheStatistics struct { type httpCodeStatistics struct { Code200 uint64 `json:"200"` Code204 uint64 `json:"204"` + Code206 uint64 `json:"206"` Code302 uint64 `json:"302"` Code400 uint64 `json:"400"` Code403 uint64 `json:"403"` @@ -79,6 +88,33 @@ type httpCodeStatistics struct { Code501 uint64 `json:"501"` } +type httpProtocolStatistics struct { + Http10 uint64 `json:"http_10"` + Http11 uint64 `json:"http_11"` + Http20 uint64 `json:"http_20"` + Http30 uint64 `json:"http_30"` + Other uint64 `json:"other"` +} + +type tlsVersionStatistics struct { + Tls12 uint64 `json:"tls_12"` + Tls13 uint64 `json:"tls_13"` + Other uint64 `json:"other"` +} + +type tlsCipherStatistics struct { + ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 uint64 `json:"ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256"` + ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 uint64 `json:"ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"` + ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 uint64 `json:"ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"` + ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 uint64 `json:"ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256"` + ECDHE_RSA_WITH_AES_256_GCM_SHA384 uint64 `json:"ECDHE_RSA_WITH_AES_256_GCM_SHA384"` + ECDHE_RSA_WITH_AES_128_GCM_SHA256 uint64 `json:"ECDHE_RSA_WITH_AES_128_GCM_SHA256"` + CHACHA20_POLY1305_SHA256 uint64 `json:"CHACHA20_POLY1305_SHA256"` + AES_128_GCM_SHA256 uint64 `json:"AES_128_GCM_SHA256"` + AES_256_GCM_SHA384 uint64 `json:"AES_256_GCM_SHA384"` + Other uint64 `json:"other"` +} + var globalStatistics statistics func getFirstValidContentEntry(entries *[]ContentEntry) *ContentEntry { @@ -180,6 +216,8 @@ func GetMimeTypeFromExtension(ext string) string { return "text/javascript" case "wasm": return "application/wasm" + case "json": + return "application/json" case "html": return "text/html" case "css": @@ -200,6 +238,73 @@ func GetMimeTypeFromExtension(ext string) string { } func handleQueryRequest(ctx httputils.RequestContext, identifier cid.Cid, extraArguments []string) { + + major, minor, _ := http.ParseHTTPVersion(ctx.GetProtocol()) + + switch major { + case 1: + switch minor { + case 0: + atomic.AddUint64(&globalStatistics.Http.Protocol.Http10, 1) + case 1: + atomic.AddUint64(&globalStatistics.Http.Protocol.Http11, 1) + default: + atomic.AddUint64(&globalStatistics.Http.Protocol.Other, 1) + } + case 2: + switch minor { + case 0: + atomic.AddUint64(&globalStatistics.Http.Protocol.Http20, 1) + default: + atomic.AddUint64(&globalStatistics.Http.Protocol.Other, 1) + } + case 3: + switch minor { + case 0: + atomic.AddUint64(&globalStatistics.Http.Protocol.Http30, 1) + default: + atomic.AddUint64(&globalStatistics.Http.Protocol.Other, 1) + } + default: + atomic.AddUint64(&globalStatistics.Http.Protocol.Other, 1) + } + + switch ctx.GetTLSVersion() { + case tls.VersionTLS12: + atomic.AddUint64(&globalStatistics.Tls.Version.Tls12, 1) + case tls.VersionTLS13: + atomic.AddUint64(&globalStatistics.Tls.Version.Tls13, 1) + default: + atomic.AddUint64(&globalStatistics.Tls.Version.Other, 1) + + } + + switch ctx.GetTLSCipher() { + //TLS 1.2 ciphers + case tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256: + atomic.AddUint64(&globalStatistics.Tls.Cipher.ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, 1) + case tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: + atomic.AddUint64(&globalStatistics.Tls.Cipher.ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, 1) + case tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: + atomic.AddUint64(&globalStatistics.Tls.Cipher.ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 1) + case tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256: + atomic.AddUint64(&globalStatistics.Tls.Cipher.ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, 1) + case tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: + atomic.AddUint64(&globalStatistics.Tls.Cipher.ECDHE_RSA_WITH_AES_256_GCM_SHA384, 1) + case tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: + atomic.AddUint64(&globalStatistics.Tls.Cipher.ECDHE_RSA_WITH_AES_128_GCM_SHA256, 1) + + //TLS 1.3 ciphers + case tls.TLS_CHACHA20_POLY1305_SHA256: + atomic.AddUint64(&globalStatistics.Tls.Cipher.CHACHA20_POLY1305_SHA256, 1) + case tls.TLS_AES_128_GCM_SHA256: + atomic.AddUint64(&globalStatistics.Tls.Cipher.AES_128_GCM_SHA256, 1) + case tls.TLS_AES_256_GCM_SHA384: + atomic.AddUint64(&globalStatistics.Tls.Cipher.AES_256_GCM_SHA384, 1) + default: + atomic.AddUint64(&globalStatistics.Tls.Cipher.Other, 1) + } + cTime := time.Now() var cacheEntry = tryGetCacheEntryForIdentifier(identifier) @@ -242,12 +347,12 @@ func handleQueryRequest(ctx httputils.RequestContext, identifier cid.Cid, extraA kind = "md5" } if kind != "" { - atomic.AddUint64(&globalStatistics.HttpCode.Code302, 1) + atomic.AddUint64(&globalStatistics.Http.Code.Code302, 1) ctx.DoRedirect(fmt.Sprintf("%s/%s/%s/%s", origin, kind, hex.EncodeToString(mh.Digest), strings.Join(extraArguments, "/")), http.StatusFound) return } } - atomic.AddUint64(&globalStatistics.HttpCode.Code404, 1) + atomic.AddUint64(&globalStatistics.Http.Code.Code404, 1) ctx.SetResponseCode(http.StatusNotFound) return } @@ -256,7 +361,7 @@ func handleQueryRequest(ctx httputils.RequestContext, identifier cid.Cid, extraA file, err := os.Open(cacheEntry.Entry.Path) if err != nil { - atomic.AddUint64(&globalStatistics.HttpCode.Code404, 1) + atomic.AddUint64(&globalStatistics.Http.Code.Code404, 1) ctx.SetResponseCode(http.StatusNotFound) return } @@ -286,8 +391,13 @@ func handleQueryRequest(ctx httputils.RequestContext, identifier cid.Cid, extraA ss := &statsStream{ stream: httputils.NewStreamFromFile(file), } - ctx.ServeStream(ss) runtime.SetFinalizer(ss, (*statsStream).Close) + if len(ctx.GetRequestHeader("range")) > 0 { + atomic.AddUint64(&globalStatistics.Http.Code.Code206, 1) + } else { + atomic.AddUint64(&globalStatistics.Http.Code.Code200, 1) + } + ctx.ServeStream(ss) } type statsStream struct { @@ -409,7 +519,7 @@ func IsTrustedPublicKey(key ed25519.PublicKey) bool { func handle(ctx httputils.RequestContext) { if len(ctx.GetHost()) > 0 && len(ctx.GetTLSServerName()) > 0 && strings.Split(ctx.GetHost(), ":")[0] != ctx.GetTLSServerName() { //Prevents rebinding / DNS stuff - atomic.AddUint64(&globalStatistics.HttpCode.Code404, 1) + atomic.AddUint64(&globalStatistics.Http.Code.Code404, 1) ctx.SetResponseCode(http.StatusNotFound) return } @@ -429,13 +539,15 @@ func handle(ctx httputils.RequestContext) { if ctx.GetPath() == "/stats" { ctx.SetResponseHeader("Content-Type", "application/json") - atomic.AddUint64(&globalStatistics.HttpCode.Code200, 1) + atomic.AddUint64(&globalStatistics.Http.Code.Code200, 1) ctx.SetResponseCode(http.StatusOK) statsStruct := struct { - Statistics *statistics `json:"statistics"` - TotalEntries uint64 `json:"total_entries"` - TotalSize uint64 `json:"total_size"` + Statistics *statistics `json:"statistics"` + Database struct { + TotalEntries uint64 `json:"entries"` + TotalSize uint64 `json:"size"` + } `json:"database"` }{ Statistics: &globalStatistics, } @@ -444,7 +556,7 @@ func handle(ctx httputils.RequestContext) { defer rows.Close() if rows.Next() { - rows.Scan(&statsStruct.TotalEntries, &statsStruct.TotalSize) + rows.Scan(&statsStruct.Database.TotalEntries, &statsStruct.Database.TotalSize) } } @@ -455,26 +567,26 @@ func handle(ctx httputils.RequestContext) { pathElements := strings.Split(ctx.GetPath(), "/") if len(pathElements) < 2 { - atomic.AddUint64(&globalStatistics.HttpCode.Code400, 1) + atomic.AddUint64(&globalStatistics.Http.Code.Code400, 1) ctx.SetResponseCode(http.StatusBadRequest) return } messageBytes, err := MakyuuIchaival.Bech32Encoding.DecodeString(pathElements[1]) if err != nil { - atomic.AddUint64(&globalStatistics.HttpCode.Code400, 1) + atomic.AddUint64(&globalStatistics.Http.Code.Code400, 1) ctx.SetResponseCode(http.StatusBadRequest) return } message := contentmessage.DecodeContentMessage(messageBytes) if message == nil { - atomic.AddUint64(&globalStatistics.HttpCode.Code400, 1) + atomic.AddUint64(&globalStatistics.Http.Code.Code400, 1) ctx.SetResponseCode(http.StatusBadRequest) return } if !IsTrustedPublicKey(message.PublicKey) { - atomic.AddUint64(&globalStatistics.HttpCode.Code403, 1) + atomic.AddUint64(&globalStatistics.Http.Code.Code403, 1) ctx.SetResponseCode(http.StatusForbidden) return } @@ -497,7 +609,7 @@ func handle(ctx httputils.RequestContext) { ctx.AddTiming("v", "Ed25519 Verify", cTime.Sub(pTime)) if !result { - atomic.AddUint64(&globalStatistics.HttpCode.Code403, 1) + atomic.AddUint64(&globalStatistics.Http.Code.Code403, 1) ctx.SetResponseCode(http.StatusForbidden) return } @@ -511,10 +623,10 @@ func handle(ctx httputils.RequestContext) { setOtherHeaders(ctx) setCORSHeaders(ctx) - atomic.AddUint64(&globalStatistics.HttpCode.Code204, 1) + atomic.AddUint64(&globalStatistics.Http.Code.Code204, 1) ctx.SetResponseCode(http.StatusNoContent) } else { - atomic.AddUint64(&globalStatistics.HttpCode.Code501, 1) + atomic.AddUint64(&globalStatistics.Http.Code.Code501, 1) ctx.SetResponseCode(http.StatusNotImplemented) } } diff --git a/go.mod b/go.mod index 92514ad..3b90859 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module git.gammaspectra.live/S.O.N.G/OrbitalBeat go 1.18 require ( - git.gammaspectra.live/S.O.N.G/MakyuuIchaival v0.0.0-20220609161446-6d2dcedf6cdb + git.gammaspectra.live/S.O.N.G/MakyuuIchaival v0.0.0-20220610084655-771c39a29ce8 github.com/cloudflare/circl v1.1.0 github.com/ipfs/go-cid v0.2.0 github.com/lib/pq v1.10.6 @@ -18,7 +18,7 @@ require ( github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect github.com/klauspost/compress v1.15.6 // indirect github.com/klauspost/cpuid/v2 v2.0.12 // indirect - github.com/lucas-clemente/quic-go v0.27.2 // indirect + github.com/lucas-clemente/quic-go v0.27.1-0.20220609183220-6fbc6d951a40 // indirect github.com/marten-seemann/qpack v0.2.1 // indirect github.com/marten-seemann/qtls-go1-16 v0.1.5 // indirect github.com/marten-seemann/qtls-go1-17 v0.1.2 // indirect @@ -37,12 +37,11 @@ require ( github.com/valyala/fasthttp v1.37.0 // indirect github.com/valyala/fastrand v1.1.0 // indirect golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect - golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect + golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect golang.org/x/net v0.0.0-20220607020251-c690dde0001d // indirect golang.org/x/sys v0.0.0-20220608164250-635b8c9b7f68 // indirect golang.org/x/text v0.3.7 // indirect - golang.org/x/tools v0.1.10 // indirect - golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect + golang.org/x/tools v0.1.11 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect lukechampine.com/blake3 v1.1.7 // indirect ) diff --git a/go.sum b/go.sum index 26ca1c0..073584e 100644 --- a/go.sum +++ b/go.sum @@ -7,8 +7,8 @@ dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBr dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= -git.gammaspectra.live/S.O.N.G/MakyuuIchaival v0.0.0-20220609161446-6d2dcedf6cdb h1:VNExZ2u32hQqBhk+PqtxbsQdx9NalDrvcqhM3gHff9U= -git.gammaspectra.live/S.O.N.G/MakyuuIchaival v0.0.0-20220609161446-6d2dcedf6cdb/go.mod h1:3n5dC5y2CX+78/fpEx/3NK4ySxoOycKLuaGR4tRuSyA= +git.gammaspectra.live/S.O.N.G/MakyuuIchaival v0.0.0-20220610084655-771c39a29ce8 h1:Xp0G4r0EwoqIu/s/4i7LAjGBjkaUnKtGX1TMkvMhvYs= +git.gammaspectra.live/S.O.N.G/MakyuuIchaival v0.0.0-20220610084655-771c39a29ce8/go.mod h1:hyfgWb6jQhsy7hrFDRq/sYLa3J3zYj7VvhHRC8ZLUlw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= @@ -95,8 +95,8 @@ github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs= github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lucas-clemente/quic-go v0.27.2 h1:zsMwwniyybb8B/UDNXRSYee7WpQJVOcjQEGgpw2ikXs= -github.com/lucas-clemente/quic-go v0.27.2/go.mod h1:vXgO/11FBSKM+js1NxoaQ/bPtVFYfB7uxhfHXyMhl1A= +github.com/lucas-clemente/quic-go v0.27.1-0.20220609183220-6fbc6d951a40 h1:TmAcxgYB4Vnpa8QfljlEFYv8BHfjrkZlTzQAvCSRTKU= +github.com/lucas-clemente/quic-go v0.27.1-0.20220609183220-6fbc6d951a40/go.mod h1:vXgO/11FBSKM+js1NxoaQ/bPtVFYfB7uxhfHXyMhl1A= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/marten-seemann/qpack v0.2.1 h1:jvTsT/HpCn2UZJdP+UUB53FfUUgeOyG5K1ns0OJOGVs= @@ -222,8 +222,8 @@ golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTk golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o= -golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -307,14 +307,12 @@ golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20= -golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= +golang.org/x/tools v0.1.11 h1:loJ25fNOEhSXfHrpoGj91eCUThwdNX6u24rO1xnNteY= +golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f h1:uF6paiQQebLeSXkrTqHqz0MXhXXS1KgF41eUdBNvxK0= -golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=