支付优化
This commit is contained in:
parent
6f91c8892f
commit
1c6c515e03
@ -10,6 +10,7 @@ import (
|
||||
|
||||
var Server struct {
|
||||
AppID int
|
||||
AppPath string
|
||||
LogLevel string
|
||||
LogPath string
|
||||
WSAddr string
|
||||
|
||||
@ -124,7 +124,7 @@ func GetChessIdByLvAndColor(Lv int, Color string) int {
|
||||
return Id
|
||||
}
|
||||
}
|
||||
// log.Debug("MergeDataCfg GetChessIdByLvAndColor lv:%v Color:%v not found", Lv, Color)
|
||||
log.Debug("MergeDataCfg GetChessIdByLvAndColor lv:%v Color:%v not found", Lv, Color)
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
"MaxConnNum": 20000,
|
||||
"DbName": "Merge_Pet",
|
||||
"HttpPort": ":8081",
|
||||
|
||||
"AppPath": "./app",
|
||||
"TELOGDIR" : "./teLog/",
|
||||
|
||||
"GameName": "Merge_Pet_Local",
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
package game
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"server/GoUtil"
|
||||
"server/MergeConst"
|
||||
mergeCluster "server/cluster"
|
||||
@ -19,7 +21,6 @@ import (
|
||||
"server/game/mod/order"
|
||||
"server/game/mod/playroom"
|
||||
proto "server/msg"
|
||||
"server/pay"
|
||||
"server/pkg/github.com/name5566/leaf/log"
|
||||
"sort"
|
||||
)
|
||||
@ -412,28 +413,27 @@ func GoogleVerify(p *Player, OrderSn, ProduceId, Token string) (*db.SqlChargeOrd
|
||||
if Order.PayStatus != MergeConst.ORDER_STATUS_IDLE {
|
||||
return nil, fmt.Errorf("订单已经支付")
|
||||
}
|
||||
AccessToken, err := pay.PostRefreshToken()
|
||||
cmd := exec.Command(conf.Server.AppPath+"/script/verifyOrder", ProduceId, Token)
|
||||
|
||||
// 获取命令的输出
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
OrderInfo, err := pay.GetOrder(ProduceId, Token, AccessToken.AccessToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
type VerifyData struct {
|
||||
PurchaseState int `json:"purchaseState"`
|
||||
DeveloperPayload string `json:"developerPayload"`
|
||||
OrderId string `json:"orderId"`
|
||||
ConsumptionState int `json:"consumptionState"`
|
||||
}
|
||||
if OrderInfo.ConsumptionState == pay.PAY_STATUS_FAIL {
|
||||
return nil, fmt.Errorf("订单支付失败")
|
||||
}
|
||||
if OrderInfo.DeveloperPayload != OrderSn {
|
||||
return nil, fmt.Errorf("订单号不一致")
|
||||
r := &VerifyData{}
|
||||
json.Unmarshal(output, r)
|
||||
if r.ConsumptionState != 1 {
|
||||
return nil, fmt.Errorf("订单未消费")
|
||||
}
|
||||
Order.PayStatus = MergeConst.ORDER_STATUS_PAY
|
||||
Order.PayChannelOrderId = r.OrderId
|
||||
Order.PayTime = GoUtil.Now()
|
||||
Order.PayChannelOrderId = OrderInfo.OrderId
|
||||
Order.PayStatus = OrderInfo.ConsumptionState
|
||||
Order.ProductName = ProduceId
|
||||
err = db.UpdatePlayerChargeData(Order)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return Order, nil
|
||||
}
|
||||
|
||||
|
||||
@ -5,8 +5,6 @@ import (
|
||||
"fmt"
|
||||
"math"
|
||||
"server/GoUtil"
|
||||
"server/MergeConst"
|
||||
"server/conf"
|
||||
cardCfg "server/conf/card"
|
||||
limitedTimeEventCfg "server/conf/limitedTimeEvent"
|
||||
mergeDataCfg "server/conf/mergeData"
|
||||
@ -2249,49 +2247,8 @@ func ReqShippingOrder(args []interface{}) error {
|
||||
_, player, buf := ParseArgs(args)
|
||||
req := &msg.ReqShippingOrder{}
|
||||
proto.Unmarshal(buf, req)
|
||||
OrderSn := req.OrderSn
|
||||
Status := int(req.Status)
|
||||
if Status == MergeConst.ORDER_STATUS_CANCEL { // 取消支付
|
||||
CancelOrder(player, OrderSn)
|
||||
player.PushClientRes(&msg.ResShippingOrder{
|
||||
Code: msg.RES_CODE_SUCCESS,
|
||||
Msg: "cancel success",
|
||||
})
|
||||
return nil
|
||||
}
|
||||
OrderData, err := GoogleVerify(player, OrderSn, req.ProduceId, req.Token)
|
||||
if err != nil {
|
||||
player.PushClientRes(&msg.ResShippingOrder{
|
||||
Code: msg.RES_CODE_FAIL,
|
||||
Msg: err.Error(),
|
||||
})
|
||||
return err
|
||||
}
|
||||
if OrderData.PayStatus != MergeConst.ORDER_STATUS_PAY {
|
||||
player.PushClientRes(&msg.ResShippingOrder{
|
||||
Code: msg.RES_CODE_FAIL,
|
||||
Msg: "order status error",
|
||||
})
|
||||
return errors.New("order status error")
|
||||
}
|
||||
if OrderData.PayStatus == MergeConst.ORDER_STATUS_PAY {
|
||||
Charge(player, int(OrderData.ProductId))
|
||||
OrderData.PayStatus = MergeConst.ORDER_STATUS_SHIP
|
||||
db.UpdatePlayerChargeData(OrderData)
|
||||
}
|
||||
player.PlayMod.save()
|
||||
orderDataMap := map[string]interface{}{
|
||||
"AppId": conf.Server.AppID,
|
||||
"ServerId": conf.Server.ServerID,
|
||||
"OrderId": OrderData.OrderId,
|
||||
"PayChannelOrderId": OrderData.PayChannelOrderId,
|
||||
"ProductId": OrderData.ProductId,
|
||||
"CreateTime": OrderData.CreateTime,
|
||||
"PayTime": OrderData.PayTime,
|
||||
"Price": OrderData.Price,
|
||||
"PayType": OrderData.PayType,
|
||||
}
|
||||
player.Kafka("pay", orderDataMap)
|
||||
|
||||
go TriggerShippingOrder(player, req)
|
||||
player.PushClientRes(&msg.ResShippingOrder{
|
||||
Code: msg.RES_CODE_SUCCESS,
|
||||
})
|
||||
|
||||
56
src/server/game/Trigger.go
Normal file
56
src/server/game/Trigger.go
Normal file
@ -0,0 +1,56 @@
|
||||
package game
|
||||
|
||||
import (
|
||||
"server/MergeConst"
|
||||
"server/conf"
|
||||
"server/db"
|
||||
"server/msg"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TriggerShippingOrder(player *Player, req *msg.ReqShippingOrder) {
|
||||
OrderSn := req.OrderSn
|
||||
Status := int(req.Status)
|
||||
if Status == MergeConst.ORDER_STATUS_CANCEL { // 取消支付
|
||||
CancelOrder(player, OrderSn)
|
||||
player.PushClientRes(&msg.ResShippingOrder{
|
||||
Code: msg.RES_CODE_SUCCESS,
|
||||
Msg: "cancel success",
|
||||
})
|
||||
return
|
||||
}
|
||||
OrderData := &db.SqlChargeOrderStruct{}
|
||||
var err error
|
||||
for {
|
||||
OrderData, err = GoogleVerify(player, OrderSn, req.ProduceId, req.Token)
|
||||
if err != nil {
|
||||
time.Sleep(1 * time.Second)
|
||||
continue
|
||||
}
|
||||
if OrderData.PayStatus != MergeConst.ORDER_STATUS_PAY {
|
||||
time.Sleep(1 * time.Second)
|
||||
continue
|
||||
}
|
||||
if OrderData.PayStatus == MergeConst.ORDER_STATUS_PAY {
|
||||
break
|
||||
}
|
||||
}
|
||||
player.lock.Lock()
|
||||
defer player.lock.Unlock()
|
||||
Charge(player, int(OrderData.ProductId))
|
||||
OrderData.PayStatus = MergeConst.ORDER_STATUS_SHIP
|
||||
db.UpdatePlayerChargeData(OrderData)
|
||||
player.PlayMod.save()
|
||||
orderDataMap := map[string]interface{}{
|
||||
"AppId": conf.Server.AppID,
|
||||
"ServerId": conf.Server.ServerID,
|
||||
"OrderId": OrderData.OrderId,
|
||||
"PayChannelOrderId": OrderData.PayChannelOrderId,
|
||||
"ProductId": OrderData.ProductId,
|
||||
"CreateTime": OrderData.CreateTime,
|
||||
"PayTime": OrderData.PayTime,
|
||||
"Price": OrderData.Price,
|
||||
"PayType": OrderData.PayType,
|
||||
}
|
||||
player.Kafka("pay", orderDataMap)
|
||||
}
|
||||
@ -11,31 +11,50 @@ require (
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/segmentio/kafka-go v0.4.47
|
||||
github.com/vicanso/go-charts/v2 v2.6.10
|
||||
google.golang.org/protobuf v1.35.2
|
||||
google.golang.org/api v0.217.0
|
||||
google.golang.org/protobuf v1.36.2
|
||||
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/auth v0.14.0 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.6.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||
github.com/google/s2a-go v0.1.9 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.14.1 // indirect
|
||||
github.com/klauspost/compress v1.15.9 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.15 // indirect
|
||||
github.com/wcharczuk/go-chart/v2 v2.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect
|
||||
go.opentelemetry.io/otel v1.31.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.31.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.31.0 // indirect
|
||||
golang.org/x/crypto v0.32.0 // indirect
|
||||
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 // indirect
|
||||
golang.org/x/net v0.34.0 // indirect
|
||||
golang.org/x/oauth2 v0.25.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250106144421-5f5ef82da422 // indirect
|
||||
google.golang.org/grpc v1.69.4 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/fsnotify/fsnotify v1.8.0 // direct
|
||||
golang.org/x/sys v0.13.0 // indirect
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/ThinkingDataAnalytics/go-sdk/v2 v2.0.3 //
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/uuid v1.3.1 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
)
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
cloud.google.com/go/auth v0.14.0 h1:A5C4dKV/Spdvxcl0ggWwWEzzP7AZMJSEIgrkngwhGYM=
|
||||
cloud.google.com/go/auth v0.14.0/go.mod h1:CYsoRL1PdiDuqeQpZE0bP2pnPrGqFcOkI0nldEQis+A=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.7 h1:/Lc7xODdqcEw8IrZ9SvwnlLX6j9FHQM74z6cBk9Rw6M=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.7/go.mod h1:NTbTTzfvPl1Y3V1nPpOgl2w6d/FjO7NNUQaWSox6ZMc=
|
||||
cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
|
||||
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/ThinkingDataAnalytics/go-sdk/v2 v2.0.3 h1:+2h2cOKzZgP8DmtuvkmUhOs5WfyseFTc0KLNR3EU2eA=
|
||||
@ -6,8 +12,8 @@ github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@ -15,16 +21,32 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
|
||||
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
||||
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
|
||||
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
|
||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA=
|
||||
github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q=
|
||||
github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA=
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
@ -56,8 +78,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/vicanso/go-charts/v2 v2.6.10 h1:Nb2YBekEbUBPbvohnUO1oYMy31v75brUPk6n/fq+JXw=
|
||||
github.com/vicanso/go-charts/v2 v2.6.10/go.mod h1:Ii2KDI3udTG1wPtiTnntzjlUBJVJTqNscMzh3oYHzUk=
|
||||
github.com/wcharczuk/go-chart/v2 v2.1.0 h1:tY2slqVQ6bN+yHSnDYwZebLQFkphK4WNrVwnt7CJZ2I=
|
||||
@ -69,9 +91,23 @@ github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3k
|
||||
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
|
||||
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8=
|
||||
go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY=
|
||||
go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE=
|
||||
go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE=
|
||||
go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY=
|
||||
go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk=
|
||||
go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8=
|
||||
go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys=
|
||||
go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 h1:QelT11PB4FXiDEXucrfNckHoFxwt8USGY1ajP1ZF5lM=
|
||||
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
@ -81,11 +117,16 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||
golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70=
|
||||
golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@ -93,8 +134,9 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
@ -106,15 +148,24 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io=
|
||||
google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
google.golang.org/api v0.217.0 h1:GYrUtD289o4zl1AhiTZL0jvQGa2RDLyC+kX1N/lfGOU=
|
||||
google.golang.org/api v0.217.0/go.mod h1:qMc2E8cBAbQlRypBTBWHklNJlaZZJBwDv81B1Iu8oSI=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250106144421-5f5ef82da422 h1:3UsHvIr4Wc2aW4brOaSCmcxh9ksica6fHEr8P1XhkYw=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250106144421-5f5ef82da422/go.mod h1:3ENsm/5D1mzDyhpzeRi1NR784I0BcofWBoSc5QqqMK4=
|
||||
google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A=
|
||||
google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
|
||||
google.golang.org/protobuf v1.36.2 h1:R8FeyR1/eLmkutZOM5CWghmo5itiG9z0ktFlTVLuTmU=
|
||||
google.golang.org/protobuf v1.36.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package pay
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@ -80,13 +81,18 @@ func PostRefreshToken() (*TokenInfo, error) {
|
||||
|
||||
// 获取订单信息
|
||||
func GetOrder(productId, token, accessToken string) (*OrderInfo, error) {
|
||||
client := &http.Client{}
|
||||
client := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
Certificates: []tls.Certificate{},
|
||||
},
|
||||
},
|
||||
}
|
||||
resp, err := client.Get("https://www.googleapis.com/androidpublisher/v3/applications/" +
|
||||
PackageName + "/purchases/products/" + productId + "/tokens/" + token + "?access_token=" + accessToken + "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetOrder err:%v", err)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
|
||||
382
src/server/vendor/cloud.google.com/go/auth/CHANGES.md
generated
vendored
Normal file
382
src/server/vendor/cloud.google.com/go/auth/CHANGES.md
generated
vendored
Normal file
@ -0,0 +1,382 @@
|
||||
# Changelog
|
||||
|
||||
## [0.14.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.13.0...auth/v0.14.0) (2025-01-08)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** Add universe domain support to idtoken ([#11059](https://github.com/googleapis/google-cloud-go/issues/11059)) ([72add7e](https://github.com/googleapis/google-cloud-go/commit/72add7e9f8f455af695e8ef79212a4bd3122fb3a))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth/oauth2adapt:** Update golang.org/x/net to v0.33.0 ([e9b0b69](https://github.com/googleapis/google-cloud-go/commit/e9b0b69644ea5b276cacff0a707e8a5e87efafc9))
|
||||
* **auth:** Fix copy of delegates in impersonate.NewIDTokenCredentials ([#11386](https://github.com/googleapis/google-cloud-go/issues/11386)) ([ff7ef8e](https://github.com/googleapis/google-cloud-go/commit/ff7ef8e7ade7171bce3e4f30ff10a2e9f6c27ca0)), refs [#11379](https://github.com/googleapis/google-cloud-go/issues/11379)
|
||||
* **auth:** Update golang.org/x/net to v0.33.0 ([e9b0b69](https://github.com/googleapis/google-cloud-go/commit/e9b0b69644ea5b276cacff0a707e8a5e87efafc9))
|
||||
|
||||
## [0.13.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.12.1...auth/v0.13.0) (2024-12-13)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** Add logging support ([#11079](https://github.com/googleapis/google-cloud-go/issues/11079)) ([c80e31d](https://github.com/googleapis/google-cloud-go/commit/c80e31df5ecb33a810be3dfb9d9e27ac531aa91d))
|
||||
* **auth:** Pass logger from auth layer to metadata package ([#11288](https://github.com/googleapis/google-cloud-go/issues/11288)) ([b552efd](https://github.com/googleapis/google-cloud-go/commit/b552efd6ab34e5dfded18438e0fbfd925805614f))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Check compute cred type before non-default flag for DP ([#11255](https://github.com/googleapis/google-cloud-go/issues/11255)) ([4347ca1](https://github.com/googleapis/google-cloud-go/commit/4347ca141892be8ae813399b4b437662a103bc90))
|
||||
|
||||
## [0.12.1](https://github.com/googleapis/google-cloud-go/compare/auth/v0.12.0...auth/v0.12.1) (2024-12-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Correct typo in link ([#11160](https://github.com/googleapis/google-cloud-go/issues/11160)) ([af6fb46](https://github.com/googleapis/google-cloud-go/commit/af6fb46d7cd694ddbe8c9d63bc4cdcd62b9fb2c1))
|
||||
|
||||
## [0.12.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.11.0...auth/v0.12.0) (2024-12-04)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** Add support for providing custom certificate URL ([#11006](https://github.com/googleapis/google-cloud-go/issues/11006)) ([ebf3657](https://github.com/googleapis/google-cloud-go/commit/ebf36579724afb375d3974cf1da38f703e3b7dbc)), refs [#11005](https://github.com/googleapis/google-cloud-go/issues/11005)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Ensure endpoints are present in Validator ([#11209](https://github.com/googleapis/google-cloud-go/issues/11209)) ([106cd53](https://github.com/googleapis/google-cloud-go/commit/106cd53309facaef1b8ea78376179f523f6912b9)), refs [#11006](https://github.com/googleapis/google-cloud-go/issues/11006) [#11190](https://github.com/googleapis/google-cloud-go/issues/11190) [#11189](https://github.com/googleapis/google-cloud-go/issues/11189) [#11188](https://github.com/googleapis/google-cloud-go/issues/11188)
|
||||
|
||||
## [0.11.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.10.2...auth/v0.11.0) (2024-11-21)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** Add universe domain support to mTLS ([#11159](https://github.com/googleapis/google-cloud-go/issues/11159)) ([117748b](https://github.com/googleapis/google-cloud-go/commit/117748ba1cfd4ae62a6a4feb7e30951cb2bc9344))
|
||||
|
||||
## [0.10.2](https://github.com/googleapis/google-cloud-go/compare/auth/v0.10.1...auth/v0.10.2) (2024-11-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Restore use of grpc.Dial ([#11118](https://github.com/googleapis/google-cloud-go/issues/11118)) ([2456b94](https://github.com/googleapis/google-cloud-go/commit/2456b943b7b8aaabd4d8bfb7572c0f477ae0db45)), refs [#7556](https://github.com/googleapis/google-cloud-go/issues/7556)
|
||||
|
||||
## [0.10.1](https://github.com/googleapis/google-cloud-go/compare/auth/v0.10.0...auth/v0.10.1) (2024-11-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Restore Application Default Credentials support to idtoken ([#11083](https://github.com/googleapis/google-cloud-go/issues/11083)) ([8771f2e](https://github.com/googleapis/google-cloud-go/commit/8771f2ea9807ab822083808e0678392edff3b4f2))
|
||||
* **auth:** Skip impersonate universe domain check if empty ([#11086](https://github.com/googleapis/google-cloud-go/issues/11086)) ([87159c1](https://github.com/googleapis/google-cloud-go/commit/87159c1059d4a18d1367ce62746a838a94964ab6))
|
||||
|
||||
## [0.10.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.9.9...auth/v0.10.0) (2024-10-30)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** Add universe domain support to credentials/impersonate ([#10953](https://github.com/googleapis/google-cloud-go/issues/10953)) ([e06cb64](https://github.com/googleapis/google-cloud-go/commit/e06cb6499f7eda3aef08ab18ff197016f667684b))
|
||||
|
||||
## [0.9.9](https://github.com/googleapis/google-cloud-go/compare/auth/v0.9.8...auth/v0.9.9) (2024-10-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Fallback cert lookups for missing files ([#11013](https://github.com/googleapis/google-cloud-go/issues/11013)) ([bd76695](https://github.com/googleapis/google-cloud-go/commit/bd766957ec238b7c40ddbabb369e612dc9b07313)), refs [#10844](https://github.com/googleapis/google-cloud-go/issues/10844)
|
||||
* **auth:** Replace MDS endpoint universe_domain with universe-domain ([#11000](https://github.com/googleapis/google-cloud-go/issues/11000)) ([6a1586f](https://github.com/googleapis/google-cloud-go/commit/6a1586f2ce9974684affaea84e7b629313b4d114))
|
||||
|
||||
## [0.9.8](https://github.com/googleapis/google-cloud-go/compare/auth/v0.9.7...auth/v0.9.8) (2024-10-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Restore OpenTelemetry handling in transports ([#10968](https://github.com/googleapis/google-cloud-go/issues/10968)) ([08c6d04](https://github.com/googleapis/google-cloud-go/commit/08c6d04901c1a20e219b2d86df41dbaa6d7d7b55)), refs [#10962](https://github.com/googleapis/google-cloud-go/issues/10962)
|
||||
* **auth:** Try talk to plaintext S2A if credentials can not be found for mTLS-S2A ([#10941](https://github.com/googleapis/google-cloud-go/issues/10941)) ([0f0bf2d](https://github.com/googleapis/google-cloud-go/commit/0f0bf2d18c97dd8b65bcf0099f0802b5631c6287))
|
||||
|
||||
## [0.9.7](https://github.com/googleapis/google-cloud-go/compare/auth/v0.9.6...auth/v0.9.7) (2024-10-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Restore support for non-default service accounts for DirectPath ([#10937](https://github.com/googleapis/google-cloud-go/issues/10937)) ([a38650e](https://github.com/googleapis/google-cloud-go/commit/a38650edbf420223077498cafa537aec74b37aad)), refs [#10907](https://github.com/googleapis/google-cloud-go/issues/10907)
|
||||
|
||||
## [0.9.6](https://github.com/googleapis/google-cloud-go/compare/auth/v0.9.5...auth/v0.9.6) (2024-09-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Make aws credentials provider retrieve fresh credentials ([#10920](https://github.com/googleapis/google-cloud-go/issues/10920)) ([250fbf8](https://github.com/googleapis/google-cloud-go/commit/250fbf87d858d865e399a241b7e537c4ff0c3dd8))
|
||||
|
||||
## [0.9.5](https://github.com/googleapis/google-cloud-go/compare/auth/v0.9.4...auth/v0.9.5) (2024-09-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Restore support for GOOGLE_CLOUD_UNIVERSE_DOMAIN env ([#10915](https://github.com/googleapis/google-cloud-go/issues/10915)) ([94caaaa](https://github.com/googleapis/google-cloud-go/commit/94caaaa061362d0e00ef6214afcc8a0a3e7ebfb2))
|
||||
* **auth:** Skip directpath credentials overwrite when it's not on GCE ([#10833](https://github.com/googleapis/google-cloud-go/issues/10833)) ([7e5e8d1](https://github.com/googleapis/google-cloud-go/commit/7e5e8d10b761b0a6e43e19a028528db361bc07b1))
|
||||
* **auth:** Use new context for non-blocking token refresh ([#10919](https://github.com/googleapis/google-cloud-go/issues/10919)) ([cf7102d](https://github.com/googleapis/google-cloud-go/commit/cf7102d33a21be1e5a9d47a49456b3a57c43b350))
|
||||
|
||||
## [0.9.4](https://github.com/googleapis/google-cloud-go/compare/auth/v0.9.3...auth/v0.9.4) (2024-09-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Enable self-signed JWT for non-GDU universe domain ([#10831](https://github.com/googleapis/google-cloud-go/issues/10831)) ([f9869f7](https://github.com/googleapis/google-cloud-go/commit/f9869f7903cfd34d1b97c25d0dc5669d2c5138e6))
|
||||
|
||||
## [0.9.3](https://github.com/googleapis/google-cloud-go/compare/auth/v0.9.2...auth/v0.9.3) (2024-09-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Choose quota project envvar over file when both present ([#10807](https://github.com/googleapis/google-cloud-go/issues/10807)) ([2d8dd77](https://github.com/googleapis/google-cloud-go/commit/2d8dd7700eff92d4b95027be55e26e1e7aa79181)), refs [#10804](https://github.com/googleapis/google-cloud-go/issues/10804)
|
||||
|
||||
## [0.9.2](https://github.com/googleapis/google-cloud-go/compare/auth/v0.9.1...auth/v0.9.2) (2024-08-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Handle non-Transport DefaultTransport ([#10733](https://github.com/googleapis/google-cloud-go/issues/10733)) ([98d91dc](https://github.com/googleapis/google-cloud-go/commit/98d91dc8316b247498fab41ab35e57a0446fe556)), refs [#10742](https://github.com/googleapis/google-cloud-go/issues/10742)
|
||||
* **auth:** Make sure quota option takes precedence over env/file ([#10797](https://github.com/googleapis/google-cloud-go/issues/10797)) ([f1b050d](https://github.com/googleapis/google-cloud-go/commit/f1b050d56d804b245cab048c2980d32b0eaceb4e)), refs [#10795](https://github.com/googleapis/google-cloud-go/issues/10795)
|
||||
|
||||
|
||||
### Documentation
|
||||
|
||||
* **auth:** Fix Go doc comment link ([#10751](https://github.com/googleapis/google-cloud-go/issues/10751)) ([015acfa](https://github.com/googleapis/google-cloud-go/commit/015acfab4d172650928bb1119bc2cd6307b9a437))
|
||||
|
||||
## [0.9.1](https://github.com/googleapis/google-cloud-go/compare/auth/v0.9.0...auth/v0.9.1) (2024-08-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Setting expireEarly to default when the value is 0 ([#10732](https://github.com/googleapis/google-cloud-go/issues/10732)) ([5e67869](https://github.com/googleapis/google-cloud-go/commit/5e67869a31e9e8ecb4eeebd2cfa11a761c3b1948))
|
||||
|
||||
## [0.9.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.8.1...auth/v0.9.0) (2024-08-16)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** Auth library can talk to S2A over mTLS ([#10634](https://github.com/googleapis/google-cloud-go/issues/10634)) ([5250a13](https://github.com/googleapis/google-cloud-go/commit/5250a13ec95b8d4eefbe0158f82857ff2189cb45))
|
||||
|
||||
## [0.8.1](https://github.com/googleapis/google-cloud-go/compare/auth/v0.8.0...auth/v0.8.1) (2024-08-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Make default client creation more lenient ([#10669](https://github.com/googleapis/google-cloud-go/issues/10669)) ([1afb9ee](https://github.com/googleapis/google-cloud-go/commit/1afb9ee1ee9de9810722800018133304a0ca34d1)), refs [#10638](https://github.com/googleapis/google-cloud-go/issues/10638)
|
||||
|
||||
## [0.8.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.7.3...auth/v0.8.0) (2024-08-07)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** Adds support for X509 workload identity federation ([#10373](https://github.com/googleapis/google-cloud-go/issues/10373)) ([5d07505](https://github.com/googleapis/google-cloud-go/commit/5d075056cbe27bb1da4072a26070c41f8999eb9b))
|
||||
|
||||
## [0.7.3](https://github.com/googleapis/google-cloud-go/compare/auth/v0.7.2...auth/v0.7.3) (2024-08-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth/oauth2adapt:** Update dependencies ([257c40b](https://github.com/googleapis/google-cloud-go/commit/257c40bd6d7e59730017cf32bda8823d7a232758))
|
||||
* **auth:** Disable automatic universe domain check for MDS ([#10620](https://github.com/googleapis/google-cloud-go/issues/10620)) ([7cea5ed](https://github.com/googleapis/google-cloud-go/commit/7cea5edd5a0c1e6bca558696f5607879141910e8))
|
||||
* **auth:** Update dependencies ([257c40b](https://github.com/googleapis/google-cloud-go/commit/257c40bd6d7e59730017cf32bda8823d7a232758))
|
||||
|
||||
## [0.7.2](https://github.com/googleapis/google-cloud-go/compare/auth/v0.7.1...auth/v0.7.2) (2024-07-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Use default client for universe metadata lookup ([#10551](https://github.com/googleapis/google-cloud-go/issues/10551)) ([d9046fd](https://github.com/googleapis/google-cloud-go/commit/d9046fdd1435d1ce48f374806c1def4cb5ac6cd3)), refs [#10544](https://github.com/googleapis/google-cloud-go/issues/10544)
|
||||
|
||||
## [0.7.1](https://github.com/googleapis/google-cloud-go/compare/auth/v0.7.0...auth/v0.7.1) (2024-07-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Bump google.golang.org/grpc@v1.64.1 ([8ecc4e9](https://github.com/googleapis/google-cloud-go/commit/8ecc4e9622e5bbe9b90384d5848ab816027226c5))
|
||||
|
||||
## [0.7.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.6.1...auth/v0.7.0) (2024-07-09)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** Add workload X509 cert provider as a default cert provider ([#10479](https://github.com/googleapis/google-cloud-go/issues/10479)) ([c51ee6c](https://github.com/googleapis/google-cloud-go/commit/c51ee6cf65ce05b4d501083e49d468c75ac1ea63))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth/oauth2adapt:** Bump google.golang.org/api@v0.187.0 ([8fa9e39](https://github.com/googleapis/google-cloud-go/commit/8fa9e398e512fd8533fd49060371e61b5725a85b))
|
||||
* **auth:** Bump google.golang.org/api@v0.187.0 ([8fa9e39](https://github.com/googleapis/google-cloud-go/commit/8fa9e398e512fd8533fd49060371e61b5725a85b))
|
||||
* **auth:** Check len of slices, not non-nil ([#10483](https://github.com/googleapis/google-cloud-go/issues/10483)) ([0a966a1](https://github.com/googleapis/google-cloud-go/commit/0a966a183e5f0e811977216d736d875b7233e942))
|
||||
|
||||
## [0.6.1](https://github.com/googleapis/google-cloud-go/compare/auth/v0.6.0...auth/v0.6.1) (2024-07-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Support gRPC API keys ([#10460](https://github.com/googleapis/google-cloud-go/issues/10460)) ([daa6646](https://github.com/googleapis/google-cloud-go/commit/daa6646d2af5d7fb5b30489f4934c7db89868c7c))
|
||||
* **auth:** Update http and grpc transports to support token exchange over mTLS ([#10397](https://github.com/googleapis/google-cloud-go/issues/10397)) ([c6dfdcf](https://github.com/googleapis/google-cloud-go/commit/c6dfdcf893c3f971eba15026c12db0a960ae81f2))
|
||||
|
||||
## [0.6.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.5.2...auth/v0.6.0) (2024-06-25)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** Add non-blocking token refresh for compute MDS ([#10263](https://github.com/googleapis/google-cloud-go/issues/10263)) ([9ac350d](https://github.com/googleapis/google-cloud-go/commit/9ac350da11a49b8e2174d3fc5b1a5070fec78b4e))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Return error if envvar detected file returns an error ([#10431](https://github.com/googleapis/google-cloud-go/issues/10431)) ([e52b9a7](https://github.com/googleapis/google-cloud-go/commit/e52b9a7c45468827f5d220ab00965191faeb9d05))
|
||||
|
||||
## [0.5.2](https://github.com/googleapis/google-cloud-go/compare/auth/v0.5.1...auth/v0.5.2) (2024-06-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Fetch initial token when CachedTokenProviderOptions.DisableAutoRefresh is true ([#10415](https://github.com/googleapis/google-cloud-go/issues/10415)) ([3266763](https://github.com/googleapis/google-cloud-go/commit/32667635ca2efad05cd8c087c004ca07d7406913)), refs [#10414](https://github.com/googleapis/google-cloud-go/issues/10414)
|
||||
|
||||
## [0.5.1](https://github.com/googleapis/google-cloud-go/compare/auth/v0.5.0...auth/v0.5.1) (2024-05-31)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Pass through client to 2LO and 3LO flows ([#10290](https://github.com/googleapis/google-cloud-go/issues/10290)) ([685784e](https://github.com/googleapis/google-cloud-go/commit/685784ea84358c15e9214bdecb307d37aa3b6d2f))
|
||||
|
||||
## [0.5.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.4.2...auth/v0.5.0) (2024-05-28)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** Adds X509 workload certificate provider ([#10233](https://github.com/googleapis/google-cloud-go/issues/10233)) ([17a9db7](https://github.com/googleapis/google-cloud-go/commit/17a9db73af35e3d1a7a25ac4fd1377a103de6150))
|
||||
|
||||
## [0.4.2](https://github.com/googleapis/google-cloud-go/compare/auth/v0.4.1...auth/v0.4.2) (2024-05-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Enable client certificates by default only for GDU ([#10151](https://github.com/googleapis/google-cloud-go/issues/10151)) ([7c52978](https://github.com/googleapis/google-cloud-go/commit/7c529786275a39b7e00525f7d5e7be0d963e9e15))
|
||||
* **auth:** Handle non-Transport DefaultTransport ([#10162](https://github.com/googleapis/google-cloud-go/issues/10162)) ([fa3bfdb](https://github.com/googleapis/google-cloud-go/commit/fa3bfdb23aaa45b34394a8b61e753b3587506782)), refs [#10159](https://github.com/googleapis/google-cloud-go/issues/10159)
|
||||
* **auth:** Have refresh time match docs ([#10147](https://github.com/googleapis/google-cloud-go/issues/10147)) ([bcb5568](https://github.com/googleapis/google-cloud-go/commit/bcb5568c07a54dd3d2e869d15f502b0741a609e8))
|
||||
* **auth:** Update compute token fetching error with named prefix ([#10180](https://github.com/googleapis/google-cloud-go/issues/10180)) ([4573504](https://github.com/googleapis/google-cloud-go/commit/4573504828d2928bebedc875d87650ba227829ea))
|
||||
|
||||
## [0.4.1](https://github.com/googleapis/google-cloud-go/compare/auth/v0.4.0...auth/v0.4.1) (2024-05-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Don't try to detect default creds it opt configured ([#10143](https://github.com/googleapis/google-cloud-go/issues/10143)) ([804632e](https://github.com/googleapis/google-cloud-go/commit/804632e7c5b0b85ff522f7951114485e256eb5bc))
|
||||
|
||||
## [0.4.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.3.0...auth/v0.4.0) (2024-05-07)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** Enable client certificates by default ([#10102](https://github.com/googleapis/google-cloud-go/issues/10102)) ([9013e52](https://github.com/googleapis/google-cloud-go/commit/9013e5200a6ec0f178ed91acb255481ffb073a2c))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Get s2a logic up to date ([#10093](https://github.com/googleapis/google-cloud-go/issues/10093)) ([4fe9ae4](https://github.com/googleapis/google-cloud-go/commit/4fe9ae4b7101af2a5221d6d6b2e77b479305bb06))
|
||||
|
||||
## [0.3.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.2.2...auth/v0.3.0) (2024-04-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **auth/httptransport:** Add ability to customize transport ([#10023](https://github.com/googleapis/google-cloud-go/issues/10023)) ([72c7f6b](https://github.com/googleapis/google-cloud-go/commit/72c7f6bbec3136cc7a62788fc7186bc33ef6c3b3)), refs [#9812](https://github.com/googleapis/google-cloud-go/issues/9812) [#9814](https://github.com/googleapis/google-cloud-go/issues/9814)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth/credentials:** Error on bad file name if explicitly set ([#10018](https://github.com/googleapis/google-cloud-go/issues/10018)) ([55beaa9](https://github.com/googleapis/google-cloud-go/commit/55beaa993aaf052d8be39766afc6777c3c2a0bdd)), refs [#9809](https://github.com/googleapis/google-cloud-go/issues/9809)
|
||||
|
||||
## [0.2.2](https://github.com/googleapis/google-cloud-go/compare/auth/v0.2.1...auth/v0.2.2) (2024-04-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Add internal opt to skip validation on transports ([#9999](https://github.com/googleapis/google-cloud-go/issues/9999)) ([9e20ef8](https://github.com/googleapis/google-cloud-go/commit/9e20ef89f6287d6bd03b8697d5898dc43b4a77cf)), refs [#9823](https://github.com/googleapis/google-cloud-go/issues/9823)
|
||||
* **auth:** Set secure flag for gRPC conn pools ([#10002](https://github.com/googleapis/google-cloud-go/issues/10002)) ([14e3956](https://github.com/googleapis/google-cloud-go/commit/14e3956dfd736399731b5ee8d9b178ae085cf7ba)), refs [#9833](https://github.com/googleapis/google-cloud-go/issues/9833)
|
||||
|
||||
## [0.2.1](https://github.com/googleapis/google-cloud-go/compare/auth/v0.2.0...auth/v0.2.1) (2024-04-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Default gRPC token type to Bearer if not set ([#9800](https://github.com/googleapis/google-cloud-go/issues/9800)) ([5284066](https://github.com/googleapis/google-cloud-go/commit/5284066670b6fe65d79089cfe0199c9660f87fc7))
|
||||
|
||||
## [0.2.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.1.1...auth/v0.2.0) (2024-04-15)
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
In the below mentioned commits there were a few large breaking changes since the
|
||||
last release of the module.
|
||||
|
||||
1. The `Credentials` type has been moved to the root of the module as it is
|
||||
becoming the core abstraction for the whole module.
|
||||
2. Because of the above mentioned change many functions that previously
|
||||
returned a `TokenProvider` now return `Credentials`. Similarly, these
|
||||
functions have been renamed to be more specific.
|
||||
3. Most places that used to take an optional `TokenProvider` now accept
|
||||
`Credentials`. You can make a `Credentials` from a `TokenProvider` using the
|
||||
constructor found in the `auth` package.
|
||||
4. The `detect` package has been renamed to `credentials`. With this change some
|
||||
function signatures were also updated for better readability.
|
||||
5. Derivative auth flows like `impersonate` and `downscope` have been moved to
|
||||
be under the new `credentials` package.
|
||||
|
||||
Although these changes are disruptive we think that they are for the best of the
|
||||
long-term health of the module. We do not expect any more large breaking changes
|
||||
like these in future revisions, even before 1.0.0. This version will be the
|
||||
first version of the auth library that our client libraries start to use and
|
||||
depend on.
|
||||
|
||||
### Features
|
||||
|
||||
* **auth/credentials/externalaccount:** Add default TokenURL ([#9700](https://github.com/googleapis/google-cloud-go/issues/9700)) ([81830e6](https://github.com/googleapis/google-cloud-go/commit/81830e6848ceefd055aa4d08f933d1154455a0f6))
|
||||
* **auth:** Add downscope.Options.UniverseDomain ([#9634](https://github.com/googleapis/google-cloud-go/issues/9634)) ([52cf7d7](https://github.com/googleapis/google-cloud-go/commit/52cf7d780853594291c4e34302d618299d1f5a1d))
|
||||
* **auth:** Add universe domain to grpctransport and httptransport ([#9663](https://github.com/googleapis/google-cloud-go/issues/9663)) ([67d353b](https://github.com/googleapis/google-cloud-go/commit/67d353beefe3b607c08c891876fbd95ab89e5fe3)), refs [#9670](https://github.com/googleapis/google-cloud-go/issues/9670)
|
||||
* **auth:** Add UniverseDomain to DetectOptions ([#9536](https://github.com/googleapis/google-cloud-go/issues/9536)) ([3618d3f](https://github.com/googleapis/google-cloud-go/commit/3618d3f7061615c0e189f376c75abc201203b501))
|
||||
* **auth:** Make package externalaccount public ([#9633](https://github.com/googleapis/google-cloud-go/issues/9633)) ([a0978d8](https://github.com/googleapis/google-cloud-go/commit/a0978d8e96968399940ebd7d092539772bf9caac))
|
||||
* **auth:** Move credentials to base auth package ([#9590](https://github.com/googleapis/google-cloud-go/issues/9590)) ([1a04baf](https://github.com/googleapis/google-cloud-go/commit/1a04bafa83c27342b9308d785645e1e5423ea10d))
|
||||
* **auth:** Refactor public sigs to use Credentials ([#9603](https://github.com/googleapis/google-cloud-go/issues/9603)) ([69cb240](https://github.com/googleapis/google-cloud-go/commit/69cb240c530b1f7173a9af2555c19e9a1beb56c5))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth/oauth2adapt:** Update protobuf dep to v1.33.0 ([30b038d](https://github.com/googleapis/google-cloud-go/commit/30b038d8cac0b8cd5dd4761c87f3f298760dd33a))
|
||||
* **auth:** Fix uint32 conversion ([9221c7f](https://github.com/googleapis/google-cloud-go/commit/9221c7fa12cef9d5fb7ddc92f41f1d6204971c7b))
|
||||
* **auth:** Port sts expires fix ([#9618](https://github.com/googleapis/google-cloud-go/issues/9618)) ([7bec97b](https://github.com/googleapis/google-cloud-go/commit/7bec97b2f51ed3ac4f9b88bf100d301da3f5d1bd))
|
||||
* **auth:** Read universe_domain from all credentials files ([#9632](https://github.com/googleapis/google-cloud-go/issues/9632)) ([16efbb5](https://github.com/googleapis/google-cloud-go/commit/16efbb52e39ea4a319e5ee1e95c0e0305b6d9824))
|
||||
* **auth:** Remove content-type header from idms get requests ([#9508](https://github.com/googleapis/google-cloud-go/issues/9508)) ([8589f41](https://github.com/googleapis/google-cloud-go/commit/8589f41599d265d7c3d46a3d86c9fab2329cbdd9))
|
||||
* **auth:** Update protobuf dep to v1.33.0 ([30b038d](https://github.com/googleapis/google-cloud-go/commit/30b038d8cac0b8cd5dd4761c87f3f298760dd33a))
|
||||
|
||||
## [0.1.1](https://github.com/googleapis/google-cloud-go/compare/auth/v0.1.0...auth/v0.1.1) (2024-03-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth/impersonate:** Properly send default detect params ([#9529](https://github.com/googleapis/google-cloud-go/issues/9529)) ([5b6b8be](https://github.com/googleapis/google-cloud-go/commit/5b6b8bef577f82707e51f5cc5d258d5bdf90218f)), refs [#9136](https://github.com/googleapis/google-cloud-go/issues/9136)
|
||||
* **auth:** Update grpc-go to v1.56.3 ([343cea8](https://github.com/googleapis/google-cloud-go/commit/343cea8c43b1e31ae21ad50ad31d3b0b60143f8c))
|
||||
* **auth:** Update grpc-go to v1.59.0 ([81a97b0](https://github.com/googleapis/google-cloud-go/commit/81a97b06cb28b25432e4ece595c55a9857e960b7))
|
||||
|
||||
## 0.1.0 (2023-10-18)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** Add base auth package ([#8465](https://github.com/googleapis/google-cloud-go/issues/8465)) ([6a45f26](https://github.com/googleapis/google-cloud-go/commit/6a45f26b809b64edae21f312c18d4205f96b180e))
|
||||
* **auth:** Add cert support to httptransport ([#8569](https://github.com/googleapis/google-cloud-go/issues/8569)) ([37e3435](https://github.com/googleapis/google-cloud-go/commit/37e3435f8e98595eafab481bdfcb31a4c56fa993))
|
||||
* **auth:** Add Credentials.UniverseDomain() ([#8654](https://github.com/googleapis/google-cloud-go/issues/8654)) ([af0aa1e](https://github.com/googleapis/google-cloud-go/commit/af0aa1ed8015bc8fe0dd87a7549ae029107cbdb8))
|
||||
* **auth:** Add detect package ([#8491](https://github.com/googleapis/google-cloud-go/issues/8491)) ([d977419](https://github.com/googleapis/google-cloud-go/commit/d977419a3269f6acc193df77a2136a6eb4b4add7))
|
||||
* **auth:** Add downscope package ([#8532](https://github.com/googleapis/google-cloud-go/issues/8532)) ([dda9bff](https://github.com/googleapis/google-cloud-go/commit/dda9bff8ec70e6d104901b4105d13dcaa4e2404c))
|
||||
* **auth:** Add grpctransport package ([#8625](https://github.com/googleapis/google-cloud-go/issues/8625)) ([69a8347](https://github.com/googleapis/google-cloud-go/commit/69a83470bdcc7ed10c6c36d1abc3b7cfdb8a0ee5))
|
||||
* **auth:** Add httptransport package ([#8567](https://github.com/googleapis/google-cloud-go/issues/8567)) ([6898597](https://github.com/googleapis/google-cloud-go/commit/6898597d2ea95d630fcd00fd15c58c75ea843bff))
|
||||
* **auth:** Add idtoken package ([#8580](https://github.com/googleapis/google-cloud-go/issues/8580)) ([a79e693](https://github.com/googleapis/google-cloud-go/commit/a79e693e97e4e3e1c6742099af3dbc58866d88fe))
|
||||
* **auth:** Add impersonate package ([#8578](https://github.com/googleapis/google-cloud-go/issues/8578)) ([e29ba0c](https://github.com/googleapis/google-cloud-go/commit/e29ba0cb7bd3888ab9e808087027dc5a32474c04))
|
||||
* **auth:** Add support for external accounts in detect ([#8508](https://github.com/googleapis/google-cloud-go/issues/8508)) ([62210d5](https://github.com/googleapis/google-cloud-go/commit/62210d5d3e56e8e9f35db8e6ac0defec19582507))
|
||||
* **auth:** Port external account changes ([#8697](https://github.com/googleapis/google-cloud-go/issues/8697)) ([5823db5](https://github.com/googleapis/google-cloud-go/commit/5823db5d633069999b58b9131a7f9cd77e82c899))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth/oauth2adapt:** Update golang.org/x/net to v0.17.0 ([174da47](https://github.com/googleapis/google-cloud-go/commit/174da47254fefb12921bbfc65b7829a453af6f5d))
|
||||
* **auth:** Update golang.org/x/net to v0.17.0 ([174da47](https://github.com/googleapis/google-cloud-go/commit/174da47254fefb12921bbfc65b7829a453af6f5d))
|
||||
202
src/server/vendor/cloud.google.com/go/auth/LICENSE
generated
vendored
Normal file
202
src/server/vendor/cloud.google.com/go/auth/LICENSE
generated
vendored
Normal file
@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
40
src/server/vendor/cloud.google.com/go/auth/README.md
generated
vendored
Normal file
40
src/server/vendor/cloud.google.com/go/auth/README.md
generated
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
# Google Auth Library for Go
|
||||
|
||||
[](https://pkg.go.dev/cloud.google.com/go/auth)
|
||||
|
||||
## Install
|
||||
|
||||
``` bash
|
||||
go get cloud.google.com/go/auth@latest
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
The most common way this library is used is transitively, by default, from any
|
||||
of our Go client libraries.
|
||||
|
||||
### Notable use-cases
|
||||
|
||||
- To create a credential directly please see examples in the
|
||||
[credentials](https://pkg.go.dev/cloud.google.com/go/auth/credentials)
|
||||
package.
|
||||
- To create a authenticated HTTP client please see examples in the
|
||||
[httptransport](https://pkg.go.dev/cloud.google.com/go/auth/httptransport)
|
||||
package.
|
||||
- To create a authenticated gRPC connection please see examples in the
|
||||
[grpctransport](https://pkg.go.dev/cloud.google.com/go/auth/grpctransport)
|
||||
package.
|
||||
- To create an ID token please see examples in the
|
||||
[idtoken](https://pkg.go.dev/cloud.google.com/go/auth/credentials/idtoken)
|
||||
package.
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome. Please, see the
|
||||
[CONTRIBUTING](https://github.com/GoogleCloudPlatform/google-cloud-go/blob/main/CONTRIBUTING.md)
|
||||
document for details.
|
||||
|
||||
Please note that this project is released with a Contributor Code of Conduct.
|
||||
By participating in this project you agree to abide by its terms.
|
||||
See [Contributor Code of Conduct](https://github.com/GoogleCloudPlatform/google-cloud-go/blob/main/CONTRIBUTING.md#contributor-code-of-conduct)
|
||||
for more information.
|
||||
618
src/server/vendor/cloud.google.com/go/auth/auth.go
generated
vendored
Normal file
618
src/server/vendor/cloud.google.com/go/auth/auth.go
generated
vendored
Normal file
@ -0,0 +1,618 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package auth provides utilities for managing Google Cloud credentials,
|
||||
// including functionality for creating, caching, and refreshing OAuth2 tokens.
|
||||
// It offers customizable options for different OAuth2 flows, such as 2-legged
|
||||
// (2LO) and 3-legged (3LO) OAuth, along with support for PKCE and automatic
|
||||
// token management.
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/auth/internal"
|
||||
"cloud.google.com/go/auth/internal/jwt"
|
||||
"github.com/googleapis/gax-go/v2/internallog"
|
||||
)
|
||||
|
||||
const (
|
||||
// Parameter keys for AuthCodeURL method to support PKCE.
|
||||
codeChallengeKey = "code_challenge"
|
||||
codeChallengeMethodKey = "code_challenge_method"
|
||||
|
||||
// Parameter key for Exchange method to support PKCE.
|
||||
codeVerifierKey = "code_verifier"
|
||||
|
||||
// 3 minutes and 45 seconds before expiration. The shortest MDS cache is 4 minutes,
|
||||
// so we give it 15 seconds to refresh it's cache before attempting to refresh a token.
|
||||
defaultExpiryDelta = 225 * time.Second
|
||||
|
||||
universeDomainDefault = "googleapis.com"
|
||||
)
|
||||
|
||||
// tokenState represents different states for a [Token].
|
||||
type tokenState int
|
||||
|
||||
const (
|
||||
// fresh indicates that the [Token] is valid. It is not expired or close to
|
||||
// expired, or the token has no expiry.
|
||||
fresh tokenState = iota
|
||||
// stale indicates that the [Token] is close to expired, and should be
|
||||
// refreshed. The token can be used normally.
|
||||
stale
|
||||
// invalid indicates that the [Token] is expired or invalid. The token
|
||||
// cannot be used for a normal operation.
|
||||
invalid
|
||||
)
|
||||
|
||||
var (
|
||||
defaultGrantType = "urn:ietf:params:oauth:grant-type:jwt-bearer"
|
||||
defaultHeader = &jwt.Header{Algorithm: jwt.HeaderAlgRSA256, Type: jwt.HeaderType}
|
||||
|
||||
// for testing
|
||||
timeNow = time.Now
|
||||
)
|
||||
|
||||
// TokenProvider specifies an interface for anything that can return a token.
|
||||
type TokenProvider interface {
|
||||
// Token returns a Token or an error.
|
||||
// The Token returned must be safe to use
|
||||
// concurrently.
|
||||
// The returned Token must not be modified.
|
||||
// The context provided must be sent along to any requests that are made in
|
||||
// the implementing code.
|
||||
Token(context.Context) (*Token, error)
|
||||
}
|
||||
|
||||
// Token holds the credential token used to authorized requests. All fields are
|
||||
// considered read-only.
|
||||
type Token struct {
|
||||
// Value is the token used to authorize requests. It is usually an access
|
||||
// token but may be other types of tokens such as ID tokens in some flows.
|
||||
Value string
|
||||
// Type is the type of token Value is. If uninitialized, it should be
|
||||
// assumed to be a "Bearer" token.
|
||||
Type string
|
||||
// Expiry is the time the token is set to expire.
|
||||
Expiry time.Time
|
||||
// Metadata may include, but is not limited to, the body of the token
|
||||
// response returned by the server.
|
||||
Metadata map[string]interface{} // TODO(codyoss): maybe make a method to flatten metadata to avoid []string for url.Values
|
||||
}
|
||||
|
||||
// IsValid reports that a [Token] is non-nil, has a [Token.Value], and has not
|
||||
// expired. A token is considered expired if [Token.Expiry] has passed or will
|
||||
// pass in the next 225 seconds.
|
||||
func (t *Token) IsValid() bool {
|
||||
return t.isValidWithEarlyExpiry(defaultExpiryDelta)
|
||||
}
|
||||
|
||||
// MetadataString is a convenience method for accessing string values in the
|
||||
// token's metadata. Returns an empty string if the metadata is nil or the value
|
||||
// for the given key cannot be cast to a string.
|
||||
func (t *Token) MetadataString(k string) string {
|
||||
if t.Metadata == nil {
|
||||
return ""
|
||||
}
|
||||
s, ok := t.Metadata[k].(string)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (t *Token) isValidWithEarlyExpiry(earlyExpiry time.Duration) bool {
|
||||
if t.isEmpty() {
|
||||
return false
|
||||
}
|
||||
if t.Expiry.IsZero() {
|
||||
return true
|
||||
}
|
||||
return !t.Expiry.Round(0).Add(-earlyExpiry).Before(timeNow())
|
||||
}
|
||||
|
||||
func (t *Token) isEmpty() bool {
|
||||
return t == nil || t.Value == ""
|
||||
}
|
||||
|
||||
// Credentials holds Google credentials, including
|
||||
// [Application Default Credentials].
|
||||
//
|
||||
// [Application Default Credentials]: https://developers.google.com/accounts/docs/application-default-credentials
|
||||
type Credentials struct {
|
||||
json []byte
|
||||
projectID CredentialsPropertyProvider
|
||||
quotaProjectID CredentialsPropertyProvider
|
||||
// universeDomain is the default service domain for a given Cloud universe.
|
||||
universeDomain CredentialsPropertyProvider
|
||||
|
||||
TokenProvider
|
||||
}
|
||||
|
||||
// JSON returns the bytes associated with the the file used to source
|
||||
// credentials if one was used.
|
||||
func (c *Credentials) JSON() []byte {
|
||||
return c.json
|
||||
}
|
||||
|
||||
// ProjectID returns the associated project ID from the underlying file or
|
||||
// environment.
|
||||
func (c *Credentials) ProjectID(ctx context.Context) (string, error) {
|
||||
if c.projectID == nil {
|
||||
return internal.GetProjectID(c.json, ""), nil
|
||||
}
|
||||
v, err := c.projectID.GetProperty(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return internal.GetProjectID(c.json, v), nil
|
||||
}
|
||||
|
||||
// QuotaProjectID returns the associated quota project ID from the underlying
|
||||
// file or environment.
|
||||
func (c *Credentials) QuotaProjectID(ctx context.Context) (string, error) {
|
||||
if c.quotaProjectID == nil {
|
||||
return internal.GetQuotaProject(c.json, ""), nil
|
||||
}
|
||||
v, err := c.quotaProjectID.GetProperty(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return internal.GetQuotaProject(c.json, v), nil
|
||||
}
|
||||
|
||||
// UniverseDomain returns the default service domain for a given Cloud universe.
|
||||
// The default value is "googleapis.com".
|
||||
func (c *Credentials) UniverseDomain(ctx context.Context) (string, error) {
|
||||
if c.universeDomain == nil {
|
||||
return universeDomainDefault, nil
|
||||
}
|
||||
v, err := c.universeDomain.GetProperty(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if v == "" {
|
||||
return universeDomainDefault, nil
|
||||
}
|
||||
return v, err
|
||||
}
|
||||
|
||||
// CredentialsPropertyProvider provides an implementation to fetch a property
|
||||
// value for [Credentials].
|
||||
type CredentialsPropertyProvider interface {
|
||||
GetProperty(context.Context) (string, error)
|
||||
}
|
||||
|
||||
// CredentialsPropertyFunc is a type adapter to allow the use of ordinary
|
||||
// functions as a [CredentialsPropertyProvider].
|
||||
type CredentialsPropertyFunc func(context.Context) (string, error)
|
||||
|
||||
// GetProperty loads the properly value provided the given context.
|
||||
func (p CredentialsPropertyFunc) GetProperty(ctx context.Context) (string, error) {
|
||||
return p(ctx)
|
||||
}
|
||||
|
||||
// CredentialsOptions are used to configure [Credentials].
|
||||
type CredentialsOptions struct {
|
||||
// TokenProvider is a means of sourcing a token for the credentials. Required.
|
||||
TokenProvider TokenProvider
|
||||
// JSON is the raw contents of the credentials file if sourced from a file.
|
||||
JSON []byte
|
||||
// ProjectIDProvider resolves the project ID associated with the
|
||||
// credentials.
|
||||
ProjectIDProvider CredentialsPropertyProvider
|
||||
// QuotaProjectIDProvider resolves the quota project ID associated with the
|
||||
// credentials.
|
||||
QuotaProjectIDProvider CredentialsPropertyProvider
|
||||
// UniverseDomainProvider resolves the universe domain with the credentials.
|
||||
UniverseDomainProvider CredentialsPropertyProvider
|
||||
}
|
||||
|
||||
// NewCredentials returns new [Credentials] from the provided options.
|
||||
func NewCredentials(opts *CredentialsOptions) *Credentials {
|
||||
creds := &Credentials{
|
||||
TokenProvider: opts.TokenProvider,
|
||||
json: opts.JSON,
|
||||
projectID: opts.ProjectIDProvider,
|
||||
quotaProjectID: opts.QuotaProjectIDProvider,
|
||||
universeDomain: opts.UniverseDomainProvider,
|
||||
}
|
||||
|
||||
return creds
|
||||
}
|
||||
|
||||
// CachedTokenProviderOptions provides options for configuring a cached
|
||||
// [TokenProvider].
|
||||
type CachedTokenProviderOptions struct {
|
||||
// DisableAutoRefresh makes the TokenProvider always return the same token,
|
||||
// even if it is expired. The default is false. Optional.
|
||||
DisableAutoRefresh bool
|
||||
// ExpireEarly configures the amount of time before a token expires, that it
|
||||
// should be refreshed. If unset, the default value is 3 minutes and 45
|
||||
// seconds. Optional.
|
||||
ExpireEarly time.Duration
|
||||
// DisableAsyncRefresh configures a synchronous workflow that refreshes
|
||||
// tokens in a blocking manner. The default is false. Optional.
|
||||
DisableAsyncRefresh bool
|
||||
}
|
||||
|
||||
func (ctpo *CachedTokenProviderOptions) autoRefresh() bool {
|
||||
if ctpo == nil {
|
||||
return true
|
||||
}
|
||||
return !ctpo.DisableAutoRefresh
|
||||
}
|
||||
|
||||
func (ctpo *CachedTokenProviderOptions) expireEarly() time.Duration {
|
||||
if ctpo == nil || ctpo.ExpireEarly == 0 {
|
||||
return defaultExpiryDelta
|
||||
}
|
||||
return ctpo.ExpireEarly
|
||||
}
|
||||
|
||||
func (ctpo *CachedTokenProviderOptions) blockingRefresh() bool {
|
||||
if ctpo == nil {
|
||||
return false
|
||||
}
|
||||
return ctpo.DisableAsyncRefresh
|
||||
}
|
||||
|
||||
// NewCachedTokenProvider wraps a [TokenProvider] to cache the tokens returned
|
||||
// by the underlying provider. By default it will refresh tokens asynchronously
|
||||
// a few minutes before they expire.
|
||||
func NewCachedTokenProvider(tp TokenProvider, opts *CachedTokenProviderOptions) TokenProvider {
|
||||
if ctp, ok := tp.(*cachedTokenProvider); ok {
|
||||
return ctp
|
||||
}
|
||||
return &cachedTokenProvider{
|
||||
tp: tp,
|
||||
autoRefresh: opts.autoRefresh(),
|
||||
expireEarly: opts.expireEarly(),
|
||||
blockingRefresh: opts.blockingRefresh(),
|
||||
}
|
||||
}
|
||||
|
||||
type cachedTokenProvider struct {
|
||||
tp TokenProvider
|
||||
autoRefresh bool
|
||||
expireEarly time.Duration
|
||||
blockingRefresh bool
|
||||
|
||||
mu sync.Mutex
|
||||
cachedToken *Token
|
||||
// isRefreshRunning ensures that the non-blocking refresh will only be
|
||||
// attempted once, even if multiple callers enter the Token method.
|
||||
isRefreshRunning bool
|
||||
// isRefreshErr ensures that the non-blocking refresh will only be attempted
|
||||
// once per refresh window if an error is encountered.
|
||||
isRefreshErr bool
|
||||
}
|
||||
|
||||
func (c *cachedTokenProvider) Token(ctx context.Context) (*Token, error) {
|
||||
if c.blockingRefresh {
|
||||
return c.tokenBlocking(ctx)
|
||||
}
|
||||
return c.tokenNonBlocking(ctx)
|
||||
}
|
||||
|
||||
func (c *cachedTokenProvider) tokenNonBlocking(ctx context.Context) (*Token, error) {
|
||||
switch c.tokenState() {
|
||||
case fresh:
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
return c.cachedToken, nil
|
||||
case stale:
|
||||
// Call tokenAsync with a new Context because the user-provided context
|
||||
// may have a short timeout incompatible with async token refresh.
|
||||
c.tokenAsync(context.Background())
|
||||
// Return the stale token immediately to not block customer requests to Cloud services.
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
return c.cachedToken, nil
|
||||
default: // invalid
|
||||
return c.tokenBlocking(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
// tokenState reports the token's validity.
|
||||
func (c *cachedTokenProvider) tokenState() tokenState {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
t := c.cachedToken
|
||||
now := timeNow()
|
||||
if t == nil || t.Value == "" {
|
||||
return invalid
|
||||
} else if t.Expiry.IsZero() {
|
||||
return fresh
|
||||
} else if now.After(t.Expiry.Round(0)) {
|
||||
return invalid
|
||||
} else if now.After(t.Expiry.Round(0).Add(-c.expireEarly)) {
|
||||
return stale
|
||||
}
|
||||
return fresh
|
||||
}
|
||||
|
||||
// tokenAsync uses a bool to ensure that only one non-blocking token refresh
|
||||
// happens at a time, even if multiple callers have entered this function
|
||||
// concurrently. This avoids creating an arbitrary number of concurrent
|
||||
// goroutines. Retries should be attempted and managed within the Token method.
|
||||
// If the refresh attempt fails, no further attempts are made until the refresh
|
||||
// window expires and the token enters the invalid state, at which point the
|
||||
// blocking call to Token should likely return the same error on the main goroutine.
|
||||
func (c *cachedTokenProvider) tokenAsync(ctx context.Context) {
|
||||
fn := func() {
|
||||
c.mu.Lock()
|
||||
c.isRefreshRunning = true
|
||||
c.mu.Unlock()
|
||||
t, err := c.tp.Token(ctx)
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.isRefreshRunning = false
|
||||
if err != nil {
|
||||
// Discard errors from the non-blocking refresh, but prevent further
|
||||
// attempts.
|
||||
c.isRefreshErr = true
|
||||
return
|
||||
}
|
||||
c.cachedToken = t
|
||||
}
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
if !c.isRefreshRunning && !c.isRefreshErr {
|
||||
go fn()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *cachedTokenProvider) tokenBlocking(ctx context.Context) (*Token, error) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.isRefreshErr = false
|
||||
if c.cachedToken.IsValid() || (!c.autoRefresh && !c.cachedToken.isEmpty()) {
|
||||
return c.cachedToken, nil
|
||||
}
|
||||
t, err := c.tp.Token(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.cachedToken = t
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// Error is a error associated with retrieving a [Token]. It can hold useful
|
||||
// additional details for debugging.
|
||||
type Error struct {
|
||||
// Response is the HTTP response associated with error. The body will always
|
||||
// be already closed and consumed.
|
||||
Response *http.Response
|
||||
// Body is the HTTP response body.
|
||||
Body []byte
|
||||
// Err is the underlying wrapped error.
|
||||
Err error
|
||||
|
||||
// code returned in the token response
|
||||
code string
|
||||
// description returned in the token response
|
||||
description string
|
||||
// uri returned in the token response
|
||||
uri string
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
if e.code != "" {
|
||||
s := fmt.Sprintf("auth: %q", e.code)
|
||||
if e.description != "" {
|
||||
s += fmt.Sprintf(" %q", e.description)
|
||||
}
|
||||
if e.uri != "" {
|
||||
s += fmt.Sprintf(" %q", e.uri)
|
||||
}
|
||||
return s
|
||||
}
|
||||
return fmt.Sprintf("auth: cannot fetch token: %v\nResponse: %s", e.Response.StatusCode, e.Body)
|
||||
}
|
||||
|
||||
// Temporary returns true if the error is considered temporary and may be able
|
||||
// to be retried.
|
||||
func (e *Error) Temporary() bool {
|
||||
if e.Response == nil {
|
||||
return false
|
||||
}
|
||||
sc := e.Response.StatusCode
|
||||
return sc == http.StatusInternalServerError || sc == http.StatusServiceUnavailable || sc == http.StatusRequestTimeout || sc == http.StatusTooManyRequests
|
||||
}
|
||||
|
||||
func (e *Error) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
// Style describes how the token endpoint wants to receive the ClientID and
|
||||
// ClientSecret.
|
||||
type Style int
|
||||
|
||||
const (
|
||||
// StyleUnknown means the value has not been initiated. Sending this in
|
||||
// a request will cause the token exchange to fail.
|
||||
StyleUnknown Style = iota
|
||||
// StyleInParams sends client info in the body of a POST request.
|
||||
StyleInParams
|
||||
// StyleInHeader sends client info using Basic Authorization header.
|
||||
StyleInHeader
|
||||
)
|
||||
|
||||
// Options2LO is the configuration settings for doing a 2-legged JWT OAuth2 flow.
|
||||
type Options2LO struct {
|
||||
// Email is the OAuth2 client ID. This value is set as the "iss" in the
|
||||
// JWT.
|
||||
Email string
|
||||
// PrivateKey contains the contents of an RSA private key or the
|
||||
// contents of a PEM file that contains a private key. It is used to sign
|
||||
// the JWT created.
|
||||
PrivateKey []byte
|
||||
// TokenURL is th URL the JWT is sent to. Required.
|
||||
TokenURL string
|
||||
// PrivateKeyID is the ID of the key used to sign the JWT. It is used as the
|
||||
// "kid" in the JWT header. Optional.
|
||||
PrivateKeyID string
|
||||
// Subject is the used for to impersonate a user. It is used as the "sub" in
|
||||
// the JWT.m Optional.
|
||||
Subject string
|
||||
// Scopes specifies requested permissions for the token. Optional.
|
||||
Scopes []string
|
||||
// Expires specifies the lifetime of the token. Optional.
|
||||
Expires time.Duration
|
||||
// Audience specifies the "aud" in the JWT. Optional.
|
||||
Audience string
|
||||
// PrivateClaims allows specifying any custom claims for the JWT. Optional.
|
||||
PrivateClaims map[string]interface{}
|
||||
|
||||
// Client is the client to be used to make the underlying token requests.
|
||||
// Optional.
|
||||
Client *http.Client
|
||||
// UseIDToken requests that the token returned be an ID token if one is
|
||||
// returned from the server. Optional.
|
||||
UseIDToken bool
|
||||
// Logger is used for debug logging. If provided, logging will be enabled
|
||||
// at the loggers configured level. By default logging is disabled unless
|
||||
// enabled by setting GOOGLE_SDK_GO_LOGGING_LEVEL in which case a default
|
||||
// logger will be used. Optional.
|
||||
Logger *slog.Logger
|
||||
}
|
||||
|
||||
func (o *Options2LO) client() *http.Client {
|
||||
if o.Client != nil {
|
||||
return o.Client
|
||||
}
|
||||
return internal.DefaultClient()
|
||||
}
|
||||
|
||||
func (o *Options2LO) validate() error {
|
||||
if o == nil {
|
||||
return errors.New("auth: options must be provided")
|
||||
}
|
||||
if o.Email == "" {
|
||||
return errors.New("auth: email must be provided")
|
||||
}
|
||||
if len(o.PrivateKey) == 0 {
|
||||
return errors.New("auth: private key must be provided")
|
||||
}
|
||||
if o.TokenURL == "" {
|
||||
return errors.New("auth: token URL must be provided")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// New2LOTokenProvider returns a [TokenProvider] from the provided options.
|
||||
func New2LOTokenProvider(opts *Options2LO) (TokenProvider, error) {
|
||||
if err := opts.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tokenProvider2LO{opts: opts, Client: opts.client(), logger: internallog.New(opts.Logger)}, nil
|
||||
}
|
||||
|
||||
type tokenProvider2LO struct {
|
||||
opts *Options2LO
|
||||
Client *http.Client
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
func (tp tokenProvider2LO) Token(ctx context.Context) (*Token, error) {
|
||||
pk, err := internal.ParseKey(tp.opts.PrivateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
claimSet := &jwt.Claims{
|
||||
Iss: tp.opts.Email,
|
||||
Scope: strings.Join(tp.opts.Scopes, " "),
|
||||
Aud: tp.opts.TokenURL,
|
||||
AdditionalClaims: tp.opts.PrivateClaims,
|
||||
Sub: tp.opts.Subject,
|
||||
}
|
||||
if t := tp.opts.Expires; t > 0 {
|
||||
claimSet.Exp = time.Now().Add(t).Unix()
|
||||
}
|
||||
if aud := tp.opts.Audience; aud != "" {
|
||||
claimSet.Aud = aud
|
||||
}
|
||||
h := *defaultHeader
|
||||
h.KeyID = tp.opts.PrivateKeyID
|
||||
payload, err := jwt.EncodeJWS(&h, claimSet, pk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v := url.Values{}
|
||||
v.Set("grant_type", defaultGrantType)
|
||||
v.Set("assertion", payload)
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", tp.opts.TokenURL, strings.NewReader(v.Encode()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
tp.logger.DebugContext(ctx, "2LO token request", "request", internallog.HTTPRequest(req, []byte(v.Encode())))
|
||||
resp, body, err := internal.DoRequest(tp.Client, req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("auth: cannot fetch token: %w", err)
|
||||
}
|
||||
tp.logger.DebugContext(ctx, "2LO token response", "response", internallog.HTTPResponse(resp, body))
|
||||
if c := resp.StatusCode; c < http.StatusOK || c >= http.StatusMultipleChoices {
|
||||
return nil, &Error{
|
||||
Response: resp,
|
||||
Body: body,
|
||||
}
|
||||
}
|
||||
// tokenRes is the JSON response body.
|
||||
var tokenRes struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
TokenType string `json:"token_type"`
|
||||
IDToken string `json:"id_token"`
|
||||
ExpiresIn int64 `json:"expires_in"`
|
||||
}
|
||||
if err := json.Unmarshal(body, &tokenRes); err != nil {
|
||||
return nil, fmt.Errorf("auth: cannot fetch token: %w", err)
|
||||
}
|
||||
token := &Token{
|
||||
Value: tokenRes.AccessToken,
|
||||
Type: tokenRes.TokenType,
|
||||
}
|
||||
token.Metadata = make(map[string]interface{})
|
||||
json.Unmarshal(body, &token.Metadata) // no error checks for optional fields
|
||||
|
||||
if secs := tokenRes.ExpiresIn; secs > 0 {
|
||||
token.Expiry = time.Now().Add(time.Duration(secs) * time.Second)
|
||||
}
|
||||
if v := tokenRes.IDToken; v != "" {
|
||||
// decode returned id token to get expiry
|
||||
claimSet, err := jwt.DecodeJWS(v)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("auth: error decoding JWT token: %w", err)
|
||||
}
|
||||
token.Expiry = time.Unix(claimSet.Exp, 0)
|
||||
}
|
||||
if tp.opts.UseIDToken {
|
||||
if tokenRes.IDToken == "" {
|
||||
return nil, fmt.Errorf("auth: response doesn't have JWT token")
|
||||
}
|
||||
token.Value = tokenRes.IDToken
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
90
src/server/vendor/cloud.google.com/go/auth/credentials/compute.go
generated
vendored
Normal file
90
src/server/vendor/cloud.google.com/go/auth/credentials/compute.go
generated
vendored
Normal file
@ -0,0 +1,90 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package credentials
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/auth"
|
||||
"cloud.google.com/go/compute/metadata"
|
||||
)
|
||||
|
||||
var (
|
||||
computeTokenMetadata = map[string]interface{}{
|
||||
"auth.google.tokenSource": "compute-metadata",
|
||||
"auth.google.serviceAccount": "default",
|
||||
}
|
||||
computeTokenURI = "instance/service-accounts/default/token"
|
||||
)
|
||||
|
||||
// computeTokenProvider creates a [cloud.google.com/go/auth.TokenProvider] that
|
||||
// uses the metadata service to retrieve tokens.
|
||||
func computeTokenProvider(opts *DetectOptions, client *metadata.Client) auth.TokenProvider {
|
||||
return auth.NewCachedTokenProvider(&computeProvider{
|
||||
scopes: opts.Scopes,
|
||||
client: client,
|
||||
}, &auth.CachedTokenProviderOptions{
|
||||
ExpireEarly: opts.EarlyTokenRefresh,
|
||||
DisableAsyncRefresh: opts.DisableAsyncRefresh,
|
||||
})
|
||||
}
|
||||
|
||||
// computeProvider fetches tokens from the google cloud metadata service.
|
||||
type computeProvider struct {
|
||||
scopes []string
|
||||
client *metadata.Client
|
||||
}
|
||||
|
||||
type metadataTokenResp struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
ExpiresInSec int `json:"expires_in"`
|
||||
TokenType string `json:"token_type"`
|
||||
}
|
||||
|
||||
func (cs *computeProvider) Token(ctx context.Context) (*auth.Token, error) {
|
||||
tokenURI, err := url.Parse(computeTokenURI)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(cs.scopes) > 0 {
|
||||
v := url.Values{}
|
||||
v.Set("scopes", strings.Join(cs.scopes, ","))
|
||||
tokenURI.RawQuery = v.Encode()
|
||||
}
|
||||
tokenJSON, err := cs.client.GetWithContext(ctx, tokenURI.String())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("credentials: cannot fetch token: %w", err)
|
||||
}
|
||||
var res metadataTokenResp
|
||||
if err := json.NewDecoder(strings.NewReader(tokenJSON)).Decode(&res); err != nil {
|
||||
return nil, fmt.Errorf("credentials: invalid token JSON from metadata: %w", err)
|
||||
}
|
||||
if res.ExpiresInSec == 0 || res.AccessToken == "" {
|
||||
return nil, errors.New("credentials: incomplete token received from metadata")
|
||||
}
|
||||
return &auth.Token{
|
||||
Value: res.AccessToken,
|
||||
Type: res.TokenType,
|
||||
Expiry: time.Now().Add(time.Duration(res.ExpiresInSec) * time.Second),
|
||||
Metadata: computeTokenMetadata,
|
||||
}, nil
|
||||
|
||||
}
|
||||
279
src/server/vendor/cloud.google.com/go/auth/credentials/detect.go
generated
vendored
Normal file
279
src/server/vendor/cloud.google.com/go/auth/credentials/detect.go
generated
vendored
Normal file
@ -0,0 +1,279 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package credentials
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/auth"
|
||||
"cloud.google.com/go/auth/internal"
|
||||
"cloud.google.com/go/auth/internal/credsfile"
|
||||
"cloud.google.com/go/compute/metadata"
|
||||
"github.com/googleapis/gax-go/v2/internallog"
|
||||
)
|
||||
|
||||
const (
|
||||
// jwtTokenURL is Google's OAuth 2.0 token URL to use with the JWT(2LO) flow.
|
||||
jwtTokenURL = "https://oauth2.googleapis.com/token"
|
||||
|
||||
// Google's OAuth 2.0 default endpoints.
|
||||
googleAuthURL = "https://accounts.google.com/o/oauth2/auth"
|
||||
googleTokenURL = "https://oauth2.googleapis.com/token"
|
||||
|
||||
// GoogleMTLSTokenURL is Google's default OAuth2.0 mTLS endpoint.
|
||||
GoogleMTLSTokenURL = "https://oauth2.mtls.googleapis.com/token"
|
||||
|
||||
// Help on default credentials
|
||||
adcSetupURL = "https://cloud.google.com/docs/authentication/external/set-up-adc"
|
||||
)
|
||||
|
||||
var (
|
||||
// for testing
|
||||
allowOnGCECheck = true
|
||||
)
|
||||
|
||||
// OnGCE reports whether this process is running in Google Cloud.
|
||||
func OnGCE() bool {
|
||||
// TODO(codyoss): once all libs use this auth lib move metadata check here
|
||||
return allowOnGCECheck && metadata.OnGCE()
|
||||
}
|
||||
|
||||
// DetectDefault searches for "Application Default Credentials" and returns
|
||||
// a credential based on the [DetectOptions] provided.
|
||||
//
|
||||
// It looks for credentials in the following places, preferring the first
|
||||
// location found:
|
||||
//
|
||||
// - A JSON file whose path is specified by the GOOGLE_APPLICATION_CREDENTIALS
|
||||
// environment variable. For workload identity federation, refer to
|
||||
// https://cloud.google.com/iam/docs/how-to#using-workload-identity-federation
|
||||
// on how to generate the JSON configuration file for on-prem/non-Google
|
||||
// cloud platforms.
|
||||
// - A JSON file in a location known to the gcloud command-line tool. On
|
||||
// Windows, this is %APPDATA%/gcloud/application_default_credentials.json. On
|
||||
// other systems, $HOME/.config/gcloud/application_default_credentials.json.
|
||||
// - On Google Compute Engine, Google App Engine standard second generation
|
||||
// runtimes, and Google App Engine flexible environment, it fetches
|
||||
// credentials from the metadata server.
|
||||
func DetectDefault(opts *DetectOptions) (*auth.Credentials, error) {
|
||||
if err := opts.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(opts.CredentialsJSON) > 0 {
|
||||
return readCredentialsFileJSON(opts.CredentialsJSON, opts)
|
||||
}
|
||||
if opts.CredentialsFile != "" {
|
||||
return readCredentialsFile(opts.CredentialsFile, opts)
|
||||
}
|
||||
if filename := os.Getenv(credsfile.GoogleAppCredsEnvVar); filename != "" {
|
||||
creds, err := readCredentialsFile(filename, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return creds, nil
|
||||
}
|
||||
|
||||
fileName := credsfile.GetWellKnownFileName()
|
||||
if b, err := os.ReadFile(fileName); err == nil {
|
||||
return readCredentialsFileJSON(b, opts)
|
||||
}
|
||||
|
||||
if OnGCE() {
|
||||
metadataClient := metadata.NewWithOptions(&metadata.Options{
|
||||
Logger: opts.logger(),
|
||||
})
|
||||
return auth.NewCredentials(&auth.CredentialsOptions{
|
||||
TokenProvider: computeTokenProvider(opts, metadataClient),
|
||||
ProjectIDProvider: auth.CredentialsPropertyFunc(func(ctx context.Context) (string, error) {
|
||||
return metadataClient.ProjectIDWithContext(ctx)
|
||||
}),
|
||||
UniverseDomainProvider: &internal.ComputeUniverseDomainProvider{
|
||||
MetadataClient: metadataClient,
|
||||
},
|
||||
}), nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("credentials: could not find default credentials. See %v for more information", adcSetupURL)
|
||||
}
|
||||
|
||||
// DetectOptions provides configuration for [DetectDefault].
|
||||
type DetectOptions struct {
|
||||
// Scopes that credentials tokens should have. Example:
|
||||
// https://www.googleapis.com/auth/cloud-platform. Required if Audience is
|
||||
// not provided.
|
||||
Scopes []string
|
||||
// Audience that credentials tokens should have. Only applicable for 2LO
|
||||
// flows with service accounts. If specified, scopes should not be provided.
|
||||
Audience string
|
||||
// Subject is the user email used for [domain wide delegation](https://developers.google.com/identity/protocols/oauth2/service-account#delegatingauthority).
|
||||
// Optional.
|
||||
Subject string
|
||||
// EarlyTokenRefresh configures how early before a token expires that it
|
||||
// should be refreshed. Once the token’s time until expiration has entered
|
||||
// this refresh window the token is considered valid but stale. If unset,
|
||||
// the default value is 3 minutes and 45 seconds. Optional.
|
||||
EarlyTokenRefresh time.Duration
|
||||
// DisableAsyncRefresh configures a synchronous workflow that refreshes
|
||||
// stale tokens while blocking. The default is false. Optional.
|
||||
DisableAsyncRefresh bool
|
||||
// AuthHandlerOptions configures an authorization handler and other options
|
||||
// for 3LO flows. It is required, and only used, for client credential
|
||||
// flows.
|
||||
AuthHandlerOptions *auth.AuthorizationHandlerOptions
|
||||
// TokenURL allows to set the token endpoint for user credential flows. If
|
||||
// unset the default value is: https://oauth2.googleapis.com/token.
|
||||
// Optional.
|
||||
TokenURL string
|
||||
// STSAudience is the audience sent to when retrieving an STS token.
|
||||
// Currently this only used for GDCH auth flow, for which it is required.
|
||||
STSAudience string
|
||||
// CredentialsFile overrides detection logic and sources a credential file
|
||||
// from the provided filepath. If provided, CredentialsJSON must not be.
|
||||
// Optional.
|
||||
CredentialsFile string
|
||||
// CredentialsJSON overrides detection logic and uses the JSON bytes as the
|
||||
// source for the credential. If provided, CredentialsFile must not be.
|
||||
// Optional.
|
||||
CredentialsJSON []byte
|
||||
// UseSelfSignedJWT directs service account based credentials to create a
|
||||
// self-signed JWT with the private key found in the file, skipping any
|
||||
// network requests that would normally be made. Optional.
|
||||
UseSelfSignedJWT bool
|
||||
// Client configures the underlying client used to make network requests
|
||||
// when fetching tokens. Optional.
|
||||
Client *http.Client
|
||||
// UniverseDomain is the default service domain for a given Cloud universe.
|
||||
// The default value is "googleapis.com". This option is ignored for
|
||||
// authentication flows that do not support universe domain. Optional.
|
||||
UniverseDomain string
|
||||
// Logger is used for debug logging. If provided, logging will be enabled
|
||||
// at the loggers configured level. By default logging is disabled unless
|
||||
// enabled by setting GOOGLE_SDK_GO_LOGGING_LEVEL in which case a default
|
||||
// logger will be used. Optional.
|
||||
Logger *slog.Logger
|
||||
}
|
||||
|
||||
func (o *DetectOptions) validate() error {
|
||||
if o == nil {
|
||||
return errors.New("credentials: options must be provided")
|
||||
}
|
||||
if len(o.Scopes) > 0 && o.Audience != "" {
|
||||
return errors.New("credentials: both scopes and audience were provided")
|
||||
}
|
||||
if len(o.CredentialsJSON) > 0 && o.CredentialsFile != "" {
|
||||
return errors.New("credentials: both credentials file and JSON were provided")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *DetectOptions) tokenURL() string {
|
||||
if o.TokenURL != "" {
|
||||
return o.TokenURL
|
||||
}
|
||||
return googleTokenURL
|
||||
}
|
||||
|
||||
func (o *DetectOptions) scopes() []string {
|
||||
scopes := make([]string, len(o.Scopes))
|
||||
copy(scopes, o.Scopes)
|
||||
return scopes
|
||||
}
|
||||
|
||||
func (o *DetectOptions) client() *http.Client {
|
||||
if o.Client != nil {
|
||||
return o.Client
|
||||
}
|
||||
return internal.DefaultClient()
|
||||
}
|
||||
|
||||
func (o *DetectOptions) logger() *slog.Logger {
|
||||
return internallog.New(o.Logger)
|
||||
}
|
||||
|
||||
func readCredentialsFile(filename string, opts *DetectOptions) (*auth.Credentials, error) {
|
||||
b, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return readCredentialsFileJSON(b, opts)
|
||||
}
|
||||
|
||||
func readCredentialsFileJSON(b []byte, opts *DetectOptions) (*auth.Credentials, error) {
|
||||
// attempt to parse jsonData as a Google Developers Console client_credentials.json.
|
||||
config := clientCredConfigFromJSON(b, opts)
|
||||
if config != nil {
|
||||
if config.AuthHandlerOpts == nil {
|
||||
return nil, errors.New("credentials: auth handler must be specified for this credential filetype")
|
||||
}
|
||||
tp, err := auth.New3LOTokenProvider(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return auth.NewCredentials(&auth.CredentialsOptions{
|
||||
TokenProvider: tp,
|
||||
JSON: b,
|
||||
}), nil
|
||||
}
|
||||
return fileCredentials(b, opts)
|
||||
}
|
||||
|
||||
func clientCredConfigFromJSON(b []byte, opts *DetectOptions) *auth.Options3LO {
|
||||
var creds credsfile.ClientCredentialsFile
|
||||
var c *credsfile.Config3LO
|
||||
if err := json.Unmarshal(b, &creds); err != nil {
|
||||
return nil
|
||||
}
|
||||
switch {
|
||||
case creds.Web != nil:
|
||||
c = creds.Web
|
||||
case creds.Installed != nil:
|
||||
c = creds.Installed
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
if len(c.RedirectURIs) < 1 {
|
||||
return nil
|
||||
}
|
||||
var handleOpts *auth.AuthorizationHandlerOptions
|
||||
if opts.AuthHandlerOptions != nil {
|
||||
handleOpts = &auth.AuthorizationHandlerOptions{
|
||||
Handler: opts.AuthHandlerOptions.Handler,
|
||||
State: opts.AuthHandlerOptions.State,
|
||||
PKCEOpts: opts.AuthHandlerOptions.PKCEOpts,
|
||||
}
|
||||
}
|
||||
return &auth.Options3LO{
|
||||
ClientID: c.ClientID,
|
||||
ClientSecret: c.ClientSecret,
|
||||
RedirectURL: c.RedirectURIs[0],
|
||||
Scopes: opts.scopes(),
|
||||
AuthURL: c.AuthURI,
|
||||
TokenURL: c.TokenURI,
|
||||
Client: opts.client(),
|
||||
Logger: opts.logger(),
|
||||
EarlyTokenExpiry: opts.EarlyTokenRefresh,
|
||||
AuthHandlerOpts: handleOpts,
|
||||
// TODO(codyoss): refactor this out. We need to add in auto-detection
|
||||
// for this use case.
|
||||
AuthStyle: auth.StyleInParams,
|
||||
}
|
||||
}
|
||||
45
src/server/vendor/cloud.google.com/go/auth/credentials/doc.go
generated
vendored
Normal file
45
src/server/vendor/cloud.google.com/go/auth/credentials/doc.go
generated
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package credentials provides support for making OAuth2 authorized and
|
||||
// authenticated HTTP requests to Google APIs. It supports the Web server flow,
|
||||
// client-side credentials, service accounts, Google Compute Engine service
|
||||
// accounts, Google App Engine service accounts and workload identity federation
|
||||
// from non-Google cloud platforms.
|
||||
//
|
||||
// A brief overview of the package follows. For more information, please read
|
||||
// https://developers.google.com/accounts/docs/OAuth2
|
||||
// and
|
||||
// https://developers.google.com/accounts/docs/application-default-credentials.
|
||||
// For more information on using workload identity federation, refer to
|
||||
// https://cloud.google.com/iam/docs/how-to#using-workload-identity-federation.
|
||||
//
|
||||
// # Credentials
|
||||
//
|
||||
// The [cloud.google.com/go/auth.Credentials] type represents Google
|
||||
// credentials, including Application Default Credentials.
|
||||
//
|
||||
// Use [DetectDefault] to obtain Application Default Credentials.
|
||||
//
|
||||
// Application Default Credentials support workload identity federation to
|
||||
// access Google Cloud resources from non-Google Cloud platforms including Amazon
|
||||
// Web Services (AWS), Microsoft Azure or any identity provider that supports
|
||||
// OpenID Connect (OIDC). Workload identity federation is recommended for
|
||||
// non-Google Cloud environments as it avoids the need to download, manage, and
|
||||
// store service account private keys locally.
|
||||
//
|
||||
// # Workforce Identity Federation
|
||||
//
|
||||
// For more information on this feature see [cloud.google.com/go/auth/credentials/externalaccount].
|
||||
package credentials
|
||||
231
src/server/vendor/cloud.google.com/go/auth/credentials/filetypes.go
generated
vendored
Normal file
231
src/server/vendor/cloud.google.com/go/auth/credentials/filetypes.go
generated
vendored
Normal file
@ -0,0 +1,231 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package credentials
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"cloud.google.com/go/auth"
|
||||
"cloud.google.com/go/auth/credentials/internal/externalaccount"
|
||||
"cloud.google.com/go/auth/credentials/internal/externalaccountuser"
|
||||
"cloud.google.com/go/auth/credentials/internal/gdch"
|
||||
"cloud.google.com/go/auth/credentials/internal/impersonate"
|
||||
internalauth "cloud.google.com/go/auth/internal"
|
||||
"cloud.google.com/go/auth/internal/credsfile"
|
||||
)
|
||||
|
||||
func fileCredentials(b []byte, opts *DetectOptions) (*auth.Credentials, error) {
|
||||
fileType, err := credsfile.ParseFileType(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var projectID, universeDomain string
|
||||
var tp auth.TokenProvider
|
||||
switch fileType {
|
||||
case credsfile.ServiceAccountKey:
|
||||
f, err := credsfile.ParseServiceAccount(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tp, err = handleServiceAccount(f, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
projectID = f.ProjectID
|
||||
universeDomain = resolveUniverseDomain(opts.UniverseDomain, f.UniverseDomain)
|
||||
case credsfile.UserCredentialsKey:
|
||||
f, err := credsfile.ParseUserCredentials(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tp, err = handleUserCredential(f, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
universeDomain = f.UniverseDomain
|
||||
case credsfile.ExternalAccountKey:
|
||||
f, err := credsfile.ParseExternalAccount(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tp, err = handleExternalAccount(f, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
universeDomain = resolveUniverseDomain(opts.UniverseDomain, f.UniverseDomain)
|
||||
case credsfile.ExternalAccountAuthorizedUserKey:
|
||||
f, err := credsfile.ParseExternalAccountAuthorizedUser(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tp, err = handleExternalAccountAuthorizedUser(f, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
universeDomain = f.UniverseDomain
|
||||
case credsfile.ImpersonatedServiceAccountKey:
|
||||
f, err := credsfile.ParseImpersonatedServiceAccount(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tp, err = handleImpersonatedServiceAccount(f, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
universeDomain = resolveUniverseDomain(opts.UniverseDomain, f.UniverseDomain)
|
||||
case credsfile.GDCHServiceAccountKey:
|
||||
f, err := credsfile.ParseGDCHServiceAccount(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tp, err = handleGDCHServiceAccount(f, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
projectID = f.Project
|
||||
universeDomain = f.UniverseDomain
|
||||
default:
|
||||
return nil, fmt.Errorf("credentials: unsupported filetype %q", fileType)
|
||||
}
|
||||
return auth.NewCredentials(&auth.CredentialsOptions{
|
||||
TokenProvider: auth.NewCachedTokenProvider(tp, &auth.CachedTokenProviderOptions{
|
||||
ExpireEarly: opts.EarlyTokenRefresh,
|
||||
}),
|
||||
JSON: b,
|
||||
ProjectIDProvider: internalauth.StaticCredentialsProperty(projectID),
|
||||
// TODO(codyoss): only set quota project here if there was a user override
|
||||
UniverseDomainProvider: internalauth.StaticCredentialsProperty(universeDomain),
|
||||
}), nil
|
||||
}
|
||||
|
||||
// resolveUniverseDomain returns optsUniverseDomain if non-empty, in order to
|
||||
// support configuring universe-specific credentials in code. Auth flows
|
||||
// unsupported for universe domain should not use this func, but should instead
|
||||
// simply set the file universe domain on the credentials.
|
||||
func resolveUniverseDomain(optsUniverseDomain, fileUniverseDomain string) string {
|
||||
if optsUniverseDomain != "" {
|
||||
return optsUniverseDomain
|
||||
}
|
||||
return fileUniverseDomain
|
||||
}
|
||||
|
||||
func handleServiceAccount(f *credsfile.ServiceAccountFile, opts *DetectOptions) (auth.TokenProvider, error) {
|
||||
ud := resolveUniverseDomain(opts.UniverseDomain, f.UniverseDomain)
|
||||
if opts.UseSelfSignedJWT {
|
||||
return configureSelfSignedJWT(f, opts)
|
||||
} else if ud != "" && ud != internalauth.DefaultUniverseDomain {
|
||||
// For non-GDU universe domains, token exchange is impossible and services
|
||||
// must support self-signed JWTs.
|
||||
opts.UseSelfSignedJWT = true
|
||||
return configureSelfSignedJWT(f, opts)
|
||||
}
|
||||
opts2LO := &auth.Options2LO{
|
||||
Email: f.ClientEmail,
|
||||
PrivateKey: []byte(f.PrivateKey),
|
||||
PrivateKeyID: f.PrivateKeyID,
|
||||
Scopes: opts.scopes(),
|
||||
TokenURL: f.TokenURL,
|
||||
Subject: opts.Subject,
|
||||
Client: opts.client(),
|
||||
Logger: opts.logger(),
|
||||
}
|
||||
if opts2LO.TokenURL == "" {
|
||||
opts2LO.TokenURL = jwtTokenURL
|
||||
}
|
||||
return auth.New2LOTokenProvider(opts2LO)
|
||||
}
|
||||
|
||||
func handleUserCredential(f *credsfile.UserCredentialsFile, opts *DetectOptions) (auth.TokenProvider, error) {
|
||||
opts3LO := &auth.Options3LO{
|
||||
ClientID: f.ClientID,
|
||||
ClientSecret: f.ClientSecret,
|
||||
Scopes: opts.scopes(),
|
||||
AuthURL: googleAuthURL,
|
||||
TokenURL: opts.tokenURL(),
|
||||
AuthStyle: auth.StyleInParams,
|
||||
EarlyTokenExpiry: opts.EarlyTokenRefresh,
|
||||
RefreshToken: f.RefreshToken,
|
||||
Client: opts.client(),
|
||||
Logger: opts.logger(),
|
||||
}
|
||||
return auth.New3LOTokenProvider(opts3LO)
|
||||
}
|
||||
|
||||
func handleExternalAccount(f *credsfile.ExternalAccountFile, opts *DetectOptions) (auth.TokenProvider, error) {
|
||||
externalOpts := &externalaccount.Options{
|
||||
Audience: f.Audience,
|
||||
SubjectTokenType: f.SubjectTokenType,
|
||||
TokenURL: f.TokenURL,
|
||||
TokenInfoURL: f.TokenInfoURL,
|
||||
ServiceAccountImpersonationURL: f.ServiceAccountImpersonationURL,
|
||||
ClientSecret: f.ClientSecret,
|
||||
ClientID: f.ClientID,
|
||||
CredentialSource: f.CredentialSource,
|
||||
QuotaProjectID: f.QuotaProjectID,
|
||||
Scopes: opts.scopes(),
|
||||
WorkforcePoolUserProject: f.WorkforcePoolUserProject,
|
||||
Client: opts.client(),
|
||||
Logger: opts.logger(),
|
||||
IsDefaultClient: opts.Client == nil,
|
||||
}
|
||||
if f.ServiceAccountImpersonation != nil {
|
||||
externalOpts.ServiceAccountImpersonationLifetimeSeconds = f.ServiceAccountImpersonation.TokenLifetimeSeconds
|
||||
}
|
||||
return externalaccount.NewTokenProvider(externalOpts)
|
||||
}
|
||||
|
||||
func handleExternalAccountAuthorizedUser(f *credsfile.ExternalAccountAuthorizedUserFile, opts *DetectOptions) (auth.TokenProvider, error) {
|
||||
externalOpts := &externalaccountuser.Options{
|
||||
Audience: f.Audience,
|
||||
RefreshToken: f.RefreshToken,
|
||||
TokenURL: f.TokenURL,
|
||||
TokenInfoURL: f.TokenInfoURL,
|
||||
ClientID: f.ClientID,
|
||||
ClientSecret: f.ClientSecret,
|
||||
Scopes: opts.scopes(),
|
||||
Client: opts.client(),
|
||||
Logger: opts.logger(),
|
||||
}
|
||||
return externalaccountuser.NewTokenProvider(externalOpts)
|
||||
}
|
||||
|
||||
func handleImpersonatedServiceAccount(f *credsfile.ImpersonatedServiceAccountFile, opts *DetectOptions) (auth.TokenProvider, error) {
|
||||
if f.ServiceAccountImpersonationURL == "" || f.CredSource == nil {
|
||||
return nil, errors.New("missing 'source_credentials' field or 'service_account_impersonation_url' in credentials")
|
||||
}
|
||||
|
||||
tp, err := fileCredentials(f.CredSource, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return impersonate.NewTokenProvider(&impersonate.Options{
|
||||
URL: f.ServiceAccountImpersonationURL,
|
||||
Scopes: opts.scopes(),
|
||||
Tp: tp,
|
||||
Delegates: f.Delegates,
|
||||
Client: opts.client(),
|
||||
Logger: opts.logger(),
|
||||
})
|
||||
}
|
||||
|
||||
func handleGDCHServiceAccount(f *credsfile.GDCHServiceAccountFile, opts *DetectOptions) (auth.TokenProvider, error) {
|
||||
return gdch.NewTokenProvider(f, &gdch.Options{
|
||||
STSAudience: opts.STSAudience,
|
||||
Client: opts.client(),
|
||||
Logger: opts.logger(),
|
||||
})
|
||||
}
|
||||
531
src/server/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/aws_provider.go
generated
vendored
Normal file
531
src/server/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/aws_provider.go
generated
vendored
Normal file
@ -0,0 +1,531 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package externalaccount
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/auth/internal"
|
||||
"github.com/googleapis/gax-go/v2/internallog"
|
||||
)
|
||||
|
||||
var (
|
||||
// getenv aliases os.Getenv for testing
|
||||
getenv = os.Getenv
|
||||
)
|
||||
|
||||
const (
|
||||
// AWS Signature Version 4 signing algorithm identifier.
|
||||
awsAlgorithm = "AWS4-HMAC-SHA256"
|
||||
|
||||
// The termination string for the AWS credential scope value as defined in
|
||||
// https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html
|
||||
awsRequestType = "aws4_request"
|
||||
|
||||
// The AWS authorization header name for the security session token if available.
|
||||
awsSecurityTokenHeader = "x-amz-security-token"
|
||||
|
||||
// The name of the header containing the session token for metadata endpoint calls
|
||||
awsIMDSv2SessionTokenHeader = "X-aws-ec2-metadata-token"
|
||||
|
||||
awsIMDSv2SessionTTLHeader = "X-aws-ec2-metadata-token-ttl-seconds"
|
||||
|
||||
awsIMDSv2SessionTTL = "300"
|
||||
|
||||
// The AWS authorization header name for the auto-generated date.
|
||||
awsDateHeader = "x-amz-date"
|
||||
|
||||
defaultRegionalCredentialVerificationURL = "https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15"
|
||||
|
||||
// Supported AWS configuration environment variables.
|
||||
awsAccessKeyIDEnvVar = "AWS_ACCESS_KEY_ID"
|
||||
awsDefaultRegionEnvVar = "AWS_DEFAULT_REGION"
|
||||
awsRegionEnvVar = "AWS_REGION"
|
||||
awsSecretAccessKeyEnvVar = "AWS_SECRET_ACCESS_KEY"
|
||||
awsSessionTokenEnvVar = "AWS_SESSION_TOKEN"
|
||||
|
||||
awsTimeFormatLong = "20060102T150405Z"
|
||||
awsTimeFormatShort = "20060102"
|
||||
awsProviderType = "aws"
|
||||
)
|
||||
|
||||
type awsSubjectProvider struct {
|
||||
EnvironmentID string
|
||||
RegionURL string
|
||||
RegionalCredVerificationURL string
|
||||
CredVerificationURL string
|
||||
IMDSv2SessionTokenURL string
|
||||
TargetResource string
|
||||
requestSigner *awsRequestSigner
|
||||
region string
|
||||
securityCredentialsProvider AwsSecurityCredentialsProvider
|
||||
reqOpts *RequestOptions
|
||||
|
||||
Client *http.Client
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
func (sp *awsSubjectProvider) subjectToken(ctx context.Context) (string, error) {
|
||||
// Set Defaults
|
||||
if sp.RegionalCredVerificationURL == "" {
|
||||
sp.RegionalCredVerificationURL = defaultRegionalCredentialVerificationURL
|
||||
}
|
||||
headers := make(map[string]string)
|
||||
if sp.shouldUseMetadataServer() {
|
||||
awsSessionToken, err := sp.getAWSSessionToken(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if awsSessionToken != "" {
|
||||
headers[awsIMDSv2SessionTokenHeader] = awsSessionToken
|
||||
}
|
||||
}
|
||||
|
||||
awsSecurityCredentials, err := sp.getSecurityCredentials(ctx, headers)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if sp.region, err = sp.getRegion(ctx, headers); err != nil {
|
||||
return "", err
|
||||
}
|
||||
sp.requestSigner = &awsRequestSigner{
|
||||
RegionName: sp.region,
|
||||
AwsSecurityCredentials: awsSecurityCredentials,
|
||||
}
|
||||
|
||||
// Generate the signed request to AWS STS GetCallerIdentity API.
|
||||
// Use the required regional endpoint. Otherwise, the request will fail.
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", strings.Replace(sp.RegionalCredVerificationURL, "{region}", sp.region, 1), nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// The full, canonical resource name of the workload identity pool
|
||||
// provider, with or without the HTTPS prefix.
|
||||
// Including this header as part of the signature is recommended to
|
||||
// ensure data integrity.
|
||||
if sp.TargetResource != "" {
|
||||
req.Header.Set("x-goog-cloud-target-resource", sp.TargetResource)
|
||||
}
|
||||
sp.requestSigner.signRequest(req)
|
||||
|
||||
/*
|
||||
The GCP STS endpoint expects the headers to be formatted as:
|
||||
# [
|
||||
# {key: 'x-amz-date', value: '...'},
|
||||
# {key: 'Authorization', value: '...'},
|
||||
# ...
|
||||
# ]
|
||||
# And then serialized as:
|
||||
# quote(json.dumps({
|
||||
# url: '...',
|
||||
# method: 'POST',
|
||||
# headers: [{key: 'x-amz-date', value: '...'}, ...]
|
||||
# }))
|
||||
*/
|
||||
|
||||
awsSignedReq := awsRequest{
|
||||
URL: req.URL.String(),
|
||||
Method: "POST",
|
||||
}
|
||||
for headerKey, headerList := range req.Header {
|
||||
for _, headerValue := range headerList {
|
||||
awsSignedReq.Headers = append(awsSignedReq.Headers, awsRequestHeader{
|
||||
Key: headerKey,
|
||||
Value: headerValue,
|
||||
})
|
||||
}
|
||||
}
|
||||
sort.Slice(awsSignedReq.Headers, func(i, j int) bool {
|
||||
headerCompare := strings.Compare(awsSignedReq.Headers[i].Key, awsSignedReq.Headers[j].Key)
|
||||
if headerCompare == 0 {
|
||||
return strings.Compare(awsSignedReq.Headers[i].Value, awsSignedReq.Headers[j].Value) < 0
|
||||
}
|
||||
return headerCompare < 0
|
||||
})
|
||||
|
||||
result, err := json.Marshal(awsSignedReq)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return url.QueryEscape(string(result)), nil
|
||||
}
|
||||
|
||||
func (sp *awsSubjectProvider) providerType() string {
|
||||
if sp.securityCredentialsProvider != nil {
|
||||
return programmaticProviderType
|
||||
}
|
||||
return awsProviderType
|
||||
}
|
||||
|
||||
func (sp *awsSubjectProvider) getAWSSessionToken(ctx context.Context) (string, error) {
|
||||
if sp.IMDSv2SessionTokenURL == "" {
|
||||
return "", nil
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, "PUT", sp.IMDSv2SessionTokenURL, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
req.Header.Set(awsIMDSv2SessionTTLHeader, awsIMDSv2SessionTTL)
|
||||
|
||||
sp.logger.DebugContext(ctx, "aws session token request", "request", internallog.HTTPRequest(req, nil))
|
||||
resp, body, err := internal.DoRequest(sp.Client, req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
sp.logger.DebugContext(ctx, "aws session token response", "response", internallog.HTTPResponse(resp, body))
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf("credentials: unable to retrieve AWS session token: %s", body)
|
||||
}
|
||||
return string(body), nil
|
||||
}
|
||||
|
||||
func (sp *awsSubjectProvider) getRegion(ctx context.Context, headers map[string]string) (string, error) {
|
||||
if sp.securityCredentialsProvider != nil {
|
||||
return sp.securityCredentialsProvider.AwsRegion(ctx, sp.reqOpts)
|
||||
}
|
||||
if canRetrieveRegionFromEnvironment() {
|
||||
if envAwsRegion := getenv(awsRegionEnvVar); envAwsRegion != "" {
|
||||
return envAwsRegion, nil
|
||||
}
|
||||
return getenv(awsDefaultRegionEnvVar), nil
|
||||
}
|
||||
|
||||
if sp.RegionURL == "" {
|
||||
return "", errors.New("credentials: unable to determine AWS region")
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", sp.RegionURL, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for name, value := range headers {
|
||||
req.Header.Add(name, value)
|
||||
}
|
||||
sp.logger.DebugContext(ctx, "aws region request", "request", internallog.HTTPRequest(req, nil))
|
||||
resp, body, err := internal.DoRequest(sp.Client, req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
sp.logger.DebugContext(ctx, "aws region response", "response", internallog.HTTPResponse(resp, body))
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf("credentials: unable to retrieve AWS region - %s", body)
|
||||
}
|
||||
|
||||
// This endpoint will return the region in format: us-east-2b.
|
||||
// Only the us-east-2 part should be used.
|
||||
bodyLen := len(body)
|
||||
if bodyLen == 0 {
|
||||
return "", nil
|
||||
}
|
||||
return string(body[:bodyLen-1]), nil
|
||||
}
|
||||
|
||||
func (sp *awsSubjectProvider) getSecurityCredentials(ctx context.Context, headers map[string]string) (result *AwsSecurityCredentials, err error) {
|
||||
if sp.securityCredentialsProvider != nil {
|
||||
return sp.securityCredentialsProvider.AwsSecurityCredentials(ctx, sp.reqOpts)
|
||||
}
|
||||
if canRetrieveSecurityCredentialFromEnvironment() {
|
||||
return &AwsSecurityCredentials{
|
||||
AccessKeyID: getenv(awsAccessKeyIDEnvVar),
|
||||
SecretAccessKey: getenv(awsSecretAccessKeyEnvVar),
|
||||
SessionToken: getenv(awsSessionTokenEnvVar),
|
||||
}, nil
|
||||
}
|
||||
|
||||
roleName, err := sp.getMetadataRoleName(ctx, headers)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
credentials, err := sp.getMetadataSecurityCredentials(ctx, roleName, headers)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if credentials.AccessKeyID == "" {
|
||||
return result, errors.New("credentials: missing AccessKeyId credential")
|
||||
}
|
||||
if credentials.SecretAccessKey == "" {
|
||||
return result, errors.New("credentials: missing SecretAccessKey credential")
|
||||
}
|
||||
|
||||
return credentials, nil
|
||||
}
|
||||
|
||||
func (sp *awsSubjectProvider) getMetadataSecurityCredentials(ctx context.Context, roleName string, headers map[string]string) (*AwsSecurityCredentials, error) {
|
||||
var result *AwsSecurityCredentials
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/%s", sp.CredVerificationURL, roleName), nil)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
for name, value := range headers {
|
||||
req.Header.Add(name, value)
|
||||
}
|
||||
sp.logger.DebugContext(ctx, "aws security credential request", "request", internallog.HTTPRequest(req, nil))
|
||||
resp, body, err := internal.DoRequest(sp.Client, req)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
sp.logger.DebugContext(ctx, "aws security credential response", "response", internallog.HTTPResponse(resp, body))
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return result, fmt.Errorf("credentials: unable to retrieve AWS security credentials - %s", body)
|
||||
}
|
||||
if err := json.Unmarshal(body, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (sp *awsSubjectProvider) getMetadataRoleName(ctx context.Context, headers map[string]string) (string, error) {
|
||||
if sp.CredVerificationURL == "" {
|
||||
return "", errors.New("credentials: unable to determine the AWS metadata server security credentials endpoint")
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", sp.CredVerificationURL, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for name, value := range headers {
|
||||
req.Header.Add(name, value)
|
||||
}
|
||||
|
||||
sp.logger.DebugContext(ctx, "aws metadata role request", "request", internallog.HTTPRequest(req, nil))
|
||||
resp, body, err := internal.DoRequest(sp.Client, req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
sp.logger.DebugContext(ctx, "aws metadata role response", "response", internallog.HTTPResponse(resp, body))
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf("credentials: unable to retrieve AWS role name - %s", body)
|
||||
}
|
||||
return string(body), nil
|
||||
}
|
||||
|
||||
// awsRequestSigner is a utility class to sign http requests using a AWS V4 signature.
|
||||
type awsRequestSigner struct {
|
||||
RegionName string
|
||||
AwsSecurityCredentials *AwsSecurityCredentials
|
||||
}
|
||||
|
||||
// signRequest adds the appropriate headers to an http.Request
|
||||
// or returns an error if something prevented this.
|
||||
func (rs *awsRequestSigner) signRequest(req *http.Request) error {
|
||||
// req is assumed non-nil
|
||||
signedRequest := cloneRequest(req)
|
||||
timestamp := Now()
|
||||
signedRequest.Header.Set("host", requestHost(req))
|
||||
if rs.AwsSecurityCredentials.SessionToken != "" {
|
||||
signedRequest.Header.Set(awsSecurityTokenHeader, rs.AwsSecurityCredentials.SessionToken)
|
||||
}
|
||||
if signedRequest.Header.Get("date") == "" {
|
||||
signedRequest.Header.Set(awsDateHeader, timestamp.Format(awsTimeFormatLong))
|
||||
}
|
||||
authorizationCode, err := rs.generateAuthentication(signedRequest, timestamp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
signedRequest.Header.Set("Authorization", authorizationCode)
|
||||
req.Header = signedRequest.Header
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rs *awsRequestSigner) generateAuthentication(req *http.Request, timestamp time.Time) (string, error) {
|
||||
canonicalHeaderColumns, canonicalHeaderData := canonicalHeaders(req)
|
||||
dateStamp := timestamp.Format(awsTimeFormatShort)
|
||||
serviceName := ""
|
||||
|
||||
if splitHost := strings.Split(requestHost(req), "."); len(splitHost) > 0 {
|
||||
serviceName = splitHost[0]
|
||||
}
|
||||
credentialScope := strings.Join([]string{dateStamp, rs.RegionName, serviceName, awsRequestType}, "/")
|
||||
requestString, err := canonicalRequest(req, canonicalHeaderColumns, canonicalHeaderData)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
requestHash, err := getSha256([]byte(requestString))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
stringToSign := strings.Join([]string{awsAlgorithm, timestamp.Format(awsTimeFormatLong), credentialScope, requestHash}, "\n")
|
||||
signingKey := []byte("AWS4" + rs.AwsSecurityCredentials.SecretAccessKey)
|
||||
for _, signingInput := range []string{
|
||||
dateStamp, rs.RegionName, serviceName, awsRequestType, stringToSign,
|
||||
} {
|
||||
signingKey, err = getHmacSha256(signingKey, []byte(signingInput))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s Credential=%s/%s, SignedHeaders=%s, Signature=%s", awsAlgorithm, rs.AwsSecurityCredentials.AccessKeyID, credentialScope, canonicalHeaderColumns, hex.EncodeToString(signingKey)), nil
|
||||
}
|
||||
|
||||
func getSha256(input []byte) (string, error) {
|
||||
hash := sha256.New()
|
||||
if _, err := hash.Write(input); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return hex.EncodeToString(hash.Sum(nil)), nil
|
||||
}
|
||||
|
||||
func getHmacSha256(key, input []byte) ([]byte, error) {
|
||||
hash := hmac.New(sha256.New, key)
|
||||
if _, err := hash.Write(input); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return hash.Sum(nil), nil
|
||||
}
|
||||
|
||||
func cloneRequest(r *http.Request) *http.Request {
|
||||
r2 := new(http.Request)
|
||||
*r2 = *r
|
||||
if r.Header != nil {
|
||||
r2.Header = make(http.Header, len(r.Header))
|
||||
|
||||
// Find total number of values.
|
||||
headerCount := 0
|
||||
for _, headerValues := range r.Header {
|
||||
headerCount += len(headerValues)
|
||||
}
|
||||
copiedHeaders := make([]string, headerCount) // shared backing array for headers' values
|
||||
|
||||
for headerKey, headerValues := range r.Header {
|
||||
headerCount = copy(copiedHeaders, headerValues)
|
||||
r2.Header[headerKey] = copiedHeaders[:headerCount:headerCount]
|
||||
copiedHeaders = copiedHeaders[headerCount:]
|
||||
}
|
||||
}
|
||||
return r2
|
||||
}
|
||||
|
||||
func canonicalPath(req *http.Request) string {
|
||||
result := req.URL.EscapedPath()
|
||||
if result == "" {
|
||||
return "/"
|
||||
}
|
||||
return path.Clean(result)
|
||||
}
|
||||
|
||||
func canonicalQuery(req *http.Request) string {
|
||||
queryValues := req.URL.Query()
|
||||
for queryKey := range queryValues {
|
||||
sort.Strings(queryValues[queryKey])
|
||||
}
|
||||
return queryValues.Encode()
|
||||
}
|
||||
|
||||
func canonicalHeaders(req *http.Request) (string, string) {
|
||||
// Header keys need to be sorted alphabetically.
|
||||
var headers []string
|
||||
lowerCaseHeaders := make(http.Header)
|
||||
for k, v := range req.Header {
|
||||
k := strings.ToLower(k)
|
||||
if _, ok := lowerCaseHeaders[k]; ok {
|
||||
// include additional values
|
||||
lowerCaseHeaders[k] = append(lowerCaseHeaders[k], v...)
|
||||
} else {
|
||||
headers = append(headers, k)
|
||||
lowerCaseHeaders[k] = v
|
||||
}
|
||||
}
|
||||
sort.Strings(headers)
|
||||
|
||||
var fullHeaders bytes.Buffer
|
||||
for _, header := range headers {
|
||||
headerValue := strings.Join(lowerCaseHeaders[header], ",")
|
||||
fullHeaders.WriteString(header)
|
||||
fullHeaders.WriteRune(':')
|
||||
fullHeaders.WriteString(headerValue)
|
||||
fullHeaders.WriteRune('\n')
|
||||
}
|
||||
|
||||
return strings.Join(headers, ";"), fullHeaders.String()
|
||||
}
|
||||
|
||||
func requestDataHash(req *http.Request) (string, error) {
|
||||
var requestData []byte
|
||||
if req.Body != nil {
|
||||
requestBody, err := req.GetBody()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer requestBody.Close()
|
||||
|
||||
requestData, err = internal.ReadAll(requestBody)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
return getSha256(requestData)
|
||||
}
|
||||
|
||||
func requestHost(req *http.Request) string {
|
||||
if req.Host != "" {
|
||||
return req.Host
|
||||
}
|
||||
return req.URL.Host
|
||||
}
|
||||
|
||||
func canonicalRequest(req *http.Request, canonicalHeaderColumns, canonicalHeaderData string) (string, error) {
|
||||
dataHash, err := requestDataHash(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s", req.Method, canonicalPath(req), canonicalQuery(req), canonicalHeaderData, canonicalHeaderColumns, dataHash), nil
|
||||
}
|
||||
|
||||
type awsRequestHeader struct {
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type awsRequest struct {
|
||||
URL string `json:"url"`
|
||||
Method string `json:"method"`
|
||||
Headers []awsRequestHeader `json:"headers"`
|
||||
}
|
||||
|
||||
// The AWS region can be provided through AWS_REGION or AWS_DEFAULT_REGION. Only one is
|
||||
// required.
|
||||
func canRetrieveRegionFromEnvironment() bool {
|
||||
return getenv(awsRegionEnvVar) != "" || getenv(awsDefaultRegionEnvVar) != ""
|
||||
}
|
||||
|
||||
// Check if both AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY are available.
|
||||
func canRetrieveSecurityCredentialFromEnvironment() bool {
|
||||
return getenv(awsAccessKeyIDEnvVar) != "" && getenv(awsSecretAccessKeyEnvVar) != ""
|
||||
}
|
||||
|
||||
func (sp *awsSubjectProvider) shouldUseMetadataServer() bool {
|
||||
return sp.securityCredentialsProvider == nil && (!canRetrieveRegionFromEnvironment() || !canRetrieveSecurityCredentialFromEnvironment())
|
||||
}
|
||||
284
src/server/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/executable_provider.go
generated
vendored
Normal file
284
src/server/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/executable_provider.go
generated
vendored
Normal file
@ -0,0 +1,284 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package externalaccount
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/auth/internal"
|
||||
)
|
||||
|
||||
const (
|
||||
executableSupportedMaxVersion = 1
|
||||
executableDefaultTimeout = 30 * time.Second
|
||||
executableSource = "response"
|
||||
executableProviderType = "executable"
|
||||
outputFileSource = "output file"
|
||||
|
||||
allowExecutablesEnvVar = "GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES"
|
||||
|
||||
jwtTokenType = "urn:ietf:params:oauth:token-type:jwt"
|
||||
idTokenType = "urn:ietf:params:oauth:token-type:id_token"
|
||||
saml2TokenType = "urn:ietf:params:oauth:token-type:saml2"
|
||||
)
|
||||
|
||||
var (
|
||||
serviceAccountImpersonationRE = regexp.MustCompile(`https://iamcredentials..+/v1/projects/-/serviceAccounts/(.*@.*):generateAccessToken`)
|
||||
)
|
||||
|
||||
type nonCacheableError struct {
|
||||
message string
|
||||
}
|
||||
|
||||
func (nce nonCacheableError) Error() string {
|
||||
return nce.message
|
||||
}
|
||||
|
||||
// environment is a contract for testing
|
||||
type environment interface {
|
||||
existingEnv() []string
|
||||
getenv(string) string
|
||||
run(ctx context.Context, command string, env []string) ([]byte, error)
|
||||
now() time.Time
|
||||
}
|
||||
|
||||
type runtimeEnvironment struct{}
|
||||
|
||||
func (r runtimeEnvironment) existingEnv() []string {
|
||||
return os.Environ()
|
||||
}
|
||||
func (r runtimeEnvironment) getenv(key string) string {
|
||||
return os.Getenv(key)
|
||||
}
|
||||
func (r runtimeEnvironment) now() time.Time {
|
||||
return time.Now().UTC()
|
||||
}
|
||||
|
||||
func (r runtimeEnvironment) run(ctx context.Context, command string, env []string) ([]byte, error) {
|
||||
splitCommand := strings.Fields(command)
|
||||
cmd := exec.CommandContext(ctx, splitCommand[0], splitCommand[1:]...)
|
||||
cmd.Env = env
|
||||
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
return nil, context.DeadlineExceeded
|
||||
}
|
||||
if exitError, ok := err.(*exec.ExitError); ok {
|
||||
return nil, exitCodeError(exitError)
|
||||
}
|
||||
return nil, executableError(err)
|
||||
}
|
||||
|
||||
bytesStdout := bytes.TrimSpace(stdout.Bytes())
|
||||
if len(bytesStdout) > 0 {
|
||||
return bytesStdout, nil
|
||||
}
|
||||
return bytes.TrimSpace(stderr.Bytes()), nil
|
||||
}
|
||||
|
||||
type executableSubjectProvider struct {
|
||||
Command string
|
||||
Timeout time.Duration
|
||||
OutputFile string
|
||||
client *http.Client
|
||||
opts *Options
|
||||
env environment
|
||||
}
|
||||
|
||||
type executableResponse struct {
|
||||
Version int `json:"version,omitempty"`
|
||||
Success *bool `json:"success,omitempty"`
|
||||
TokenType string `json:"token_type,omitempty"`
|
||||
ExpirationTime int64 `json:"expiration_time,omitempty"`
|
||||
IDToken string `json:"id_token,omitempty"`
|
||||
SamlResponse string `json:"saml_response,omitempty"`
|
||||
Code string `json:"code,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
func (sp *executableSubjectProvider) parseSubjectTokenFromSource(response []byte, source string, now int64) (string, error) {
|
||||
var result executableResponse
|
||||
if err := json.Unmarshal(response, &result); err != nil {
|
||||
return "", jsonParsingError(source, string(response))
|
||||
}
|
||||
// Validate
|
||||
if result.Version == 0 {
|
||||
return "", missingFieldError(source, "version")
|
||||
}
|
||||
if result.Success == nil {
|
||||
return "", missingFieldError(source, "success")
|
||||
}
|
||||
if !*result.Success {
|
||||
if result.Code == "" || result.Message == "" {
|
||||
return "", malformedFailureError()
|
||||
}
|
||||
return "", userDefinedError(result.Code, result.Message)
|
||||
}
|
||||
if result.Version > executableSupportedMaxVersion || result.Version < 0 {
|
||||
return "", unsupportedVersionError(source, result.Version)
|
||||
}
|
||||
if result.ExpirationTime == 0 && sp.OutputFile != "" {
|
||||
return "", missingFieldError(source, "expiration_time")
|
||||
}
|
||||
if result.TokenType == "" {
|
||||
return "", missingFieldError(source, "token_type")
|
||||
}
|
||||
if result.ExpirationTime != 0 && result.ExpirationTime < now {
|
||||
return "", tokenExpiredError()
|
||||
}
|
||||
|
||||
switch result.TokenType {
|
||||
case jwtTokenType, idTokenType:
|
||||
if result.IDToken == "" {
|
||||
return "", missingFieldError(source, "id_token")
|
||||
}
|
||||
return result.IDToken, nil
|
||||
case saml2TokenType:
|
||||
if result.SamlResponse == "" {
|
||||
return "", missingFieldError(source, "saml_response")
|
||||
}
|
||||
return result.SamlResponse, nil
|
||||
default:
|
||||
return "", tokenTypeError(source)
|
||||
}
|
||||
}
|
||||
|
||||
func (sp *executableSubjectProvider) subjectToken(ctx context.Context) (string, error) {
|
||||
if token, err := sp.getTokenFromOutputFile(); token != "" || err != nil {
|
||||
return token, err
|
||||
}
|
||||
return sp.getTokenFromExecutableCommand(ctx)
|
||||
}
|
||||
|
||||
func (sp *executableSubjectProvider) providerType() string {
|
||||
return executableProviderType
|
||||
}
|
||||
|
||||
func (sp *executableSubjectProvider) getTokenFromOutputFile() (token string, err error) {
|
||||
if sp.OutputFile == "" {
|
||||
// This ExecutableCredentialSource doesn't use an OutputFile.
|
||||
return "", nil
|
||||
}
|
||||
|
||||
file, err := os.Open(sp.OutputFile)
|
||||
if err != nil {
|
||||
// No OutputFile found. Hasn't been created yet, so skip it.
|
||||
return "", nil
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
data, err := internal.ReadAll(file)
|
||||
if err != nil || len(data) == 0 {
|
||||
// Cachefile exists, but no data found. Get new credential.
|
||||
return "", nil
|
||||
}
|
||||
|
||||
token, err = sp.parseSubjectTokenFromSource(data, outputFileSource, sp.env.now().Unix())
|
||||
if err != nil {
|
||||
if _, ok := err.(nonCacheableError); ok {
|
||||
// If the cached token is expired we need a new token,
|
||||
// and if the cache contains a failure, we need to try again.
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// There was an error in the cached token, and the developer should be aware of it.
|
||||
return "", err
|
||||
}
|
||||
// Token parsing succeeded. Use found token.
|
||||
return token, nil
|
||||
}
|
||||
|
||||
func (sp *executableSubjectProvider) executableEnvironment() []string {
|
||||
result := sp.env.existingEnv()
|
||||
result = append(result, fmt.Sprintf("GOOGLE_EXTERNAL_ACCOUNT_AUDIENCE=%v", sp.opts.Audience))
|
||||
result = append(result, fmt.Sprintf("GOOGLE_EXTERNAL_ACCOUNT_TOKEN_TYPE=%v", sp.opts.SubjectTokenType))
|
||||
result = append(result, "GOOGLE_EXTERNAL_ACCOUNT_INTERACTIVE=0")
|
||||
if sp.opts.ServiceAccountImpersonationURL != "" {
|
||||
matches := serviceAccountImpersonationRE.FindStringSubmatch(sp.opts.ServiceAccountImpersonationURL)
|
||||
if matches != nil {
|
||||
result = append(result, fmt.Sprintf("GOOGLE_EXTERNAL_ACCOUNT_IMPERSONATED_EMAIL=%v", matches[1]))
|
||||
}
|
||||
}
|
||||
if sp.OutputFile != "" {
|
||||
result = append(result, fmt.Sprintf("GOOGLE_EXTERNAL_ACCOUNT_OUTPUT_FILE=%v", sp.OutputFile))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (sp *executableSubjectProvider) getTokenFromExecutableCommand(ctx context.Context) (string, error) {
|
||||
// For security reasons, we need our consumers to set this environment variable to allow executables to be run.
|
||||
if sp.env.getenv(allowExecutablesEnvVar) != "1" {
|
||||
return "", errors.New("credentials: executables need to be explicitly allowed (set GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES to '1') to run")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithDeadline(ctx, sp.env.now().Add(sp.Timeout))
|
||||
defer cancel()
|
||||
|
||||
output, err := sp.env.run(ctx, sp.Command, sp.executableEnvironment())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return sp.parseSubjectTokenFromSource(output, executableSource, sp.env.now().Unix())
|
||||
}
|
||||
|
||||
func missingFieldError(source, field string) error {
|
||||
return fmt.Errorf("credentials: %q missing %q field", source, field)
|
||||
}
|
||||
|
||||
func jsonParsingError(source, data string) error {
|
||||
return fmt.Errorf("credentials: unable to parse %q: %v", source, data)
|
||||
}
|
||||
|
||||
func malformedFailureError() error {
|
||||
return nonCacheableError{"credentials: response must include `error` and `message` fields when unsuccessful"}
|
||||
}
|
||||
|
||||
func userDefinedError(code, message string) error {
|
||||
return nonCacheableError{fmt.Sprintf("credentials: response contains unsuccessful response: (%v) %v", code, message)}
|
||||
}
|
||||
|
||||
func unsupportedVersionError(source string, version int) error {
|
||||
return fmt.Errorf("credentials: %v contains unsupported version: %v", source, version)
|
||||
}
|
||||
|
||||
func tokenExpiredError() error {
|
||||
return nonCacheableError{"credentials: the token returned by the executable is expired"}
|
||||
}
|
||||
|
||||
func tokenTypeError(source string) error {
|
||||
return fmt.Errorf("credentials: %v contains unsupported token type", source)
|
||||
}
|
||||
|
||||
func exitCodeError(err *exec.ExitError) error {
|
||||
return fmt.Errorf("credentials: executable command failed with exit code %v: %w", err.ExitCode(), err)
|
||||
}
|
||||
|
||||
func executableError(err error) error {
|
||||
return fmt.Errorf("credentials: executable command failed: %w", err)
|
||||
}
|
||||
428
src/server/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/externalaccount.go
generated
vendored
Normal file
428
src/server/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/externalaccount.go
generated
vendored
Normal file
@ -0,0 +1,428 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package externalaccount
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/auth"
|
||||
"cloud.google.com/go/auth/credentials/internal/impersonate"
|
||||
"cloud.google.com/go/auth/credentials/internal/stsexchange"
|
||||
"cloud.google.com/go/auth/internal/credsfile"
|
||||
"github.com/googleapis/gax-go/v2/internallog"
|
||||
)
|
||||
|
||||
const (
|
||||
timeoutMinimum = 5 * time.Second
|
||||
timeoutMaximum = 120 * time.Second
|
||||
|
||||
universeDomainPlaceholder = "UNIVERSE_DOMAIN"
|
||||
defaultTokenURL = "https://sts.UNIVERSE_DOMAIN/v1/token"
|
||||
defaultUniverseDomain = "googleapis.com"
|
||||
)
|
||||
|
||||
var (
|
||||
// Now aliases time.Now for testing
|
||||
Now = func() time.Time {
|
||||
return time.Now().UTC()
|
||||
}
|
||||
validWorkforceAudiencePattern *regexp.Regexp = regexp.MustCompile(`//iam\.googleapis\.com/locations/[^/]+/workforcePools/`)
|
||||
)
|
||||
|
||||
// Options stores the configuration for fetching tokens with external credentials.
|
||||
type Options struct {
|
||||
// Audience is the Secure Token Service (STS) audience which contains the resource name for the workload
|
||||
// identity pool or the workforce pool and the provider identifier in that pool.
|
||||
Audience string
|
||||
// SubjectTokenType is the STS token type based on the Oauth2.0 token exchange spec
|
||||
// e.g. `urn:ietf:params:oauth:token-type:jwt`.
|
||||
SubjectTokenType string
|
||||
// TokenURL is the STS token exchange endpoint.
|
||||
TokenURL string
|
||||
// TokenInfoURL is the token_info endpoint used to retrieve the account related information (
|
||||
// user attributes like account identifier, eg. email, username, uid, etc). This is
|
||||
// needed for gCloud session account identification.
|
||||
TokenInfoURL string
|
||||
// ServiceAccountImpersonationURL is the URL for the service account impersonation request. This is only
|
||||
// required for workload identity pools when APIs to be accessed have not integrated with UberMint.
|
||||
ServiceAccountImpersonationURL string
|
||||
// ServiceAccountImpersonationLifetimeSeconds is the number of seconds the service account impersonation
|
||||
// token will be valid for.
|
||||
ServiceAccountImpersonationLifetimeSeconds int
|
||||
// ClientSecret is currently only required if token_info endpoint also
|
||||
// needs to be called with the generated GCP access token. When provided, STS will be
|
||||
// called with additional basic authentication using client_id as username and client_secret as password.
|
||||
ClientSecret string
|
||||
// ClientID is only required in conjunction with ClientSecret, as described above.
|
||||
ClientID string
|
||||
// CredentialSource contains the necessary information to retrieve the token itself, as well
|
||||
// as some environmental information.
|
||||
CredentialSource *credsfile.CredentialSource
|
||||
// QuotaProjectID is injected by gCloud. If the value is non-empty, the Auth libraries
|
||||
// will set the x-goog-user-project which overrides the project associated with the credentials.
|
||||
QuotaProjectID string
|
||||
// Scopes contains the desired scopes for the returned access token.
|
||||
Scopes []string
|
||||
// WorkforcePoolUserProject should be set when it is a workforce pool and
|
||||
// not a workload identity pool. The underlying principal must still have
|
||||
// serviceusage.services.use IAM permission to use the project for
|
||||
// billing/quota. Optional.
|
||||
WorkforcePoolUserProject string
|
||||
// UniverseDomain is the default service domain for a given Cloud universe.
|
||||
// This value will be used in the default STS token URL. The default value
|
||||
// is "googleapis.com". It will not be used if TokenURL is set. Optional.
|
||||
UniverseDomain string
|
||||
// SubjectTokenProvider is an optional token provider for OIDC/SAML
|
||||
// credentials. One of SubjectTokenProvider, AWSSecurityCredentialProvider
|
||||
// or CredentialSource must be provided. Optional.
|
||||
SubjectTokenProvider SubjectTokenProvider
|
||||
// AwsSecurityCredentialsProvider is an AWS Security Credential provider
|
||||
// for AWS credentials. One of SubjectTokenProvider,
|
||||
// AWSSecurityCredentialProvider or CredentialSource must be provided. Optional.
|
||||
AwsSecurityCredentialsProvider AwsSecurityCredentialsProvider
|
||||
// Client for token request.
|
||||
Client *http.Client
|
||||
// IsDefaultClient marks whether the client passed in is a default client that can be overriden.
|
||||
// This is important for X509 credentials which should create a new client if the default was used
|
||||
// but should respect a client explicitly passed in by the user.
|
||||
IsDefaultClient bool
|
||||
// Logger is used for debug logging. If provided, logging will be enabled
|
||||
// at the loggers configured level. By default logging is disabled unless
|
||||
// enabled by setting GOOGLE_SDK_GO_LOGGING_LEVEL in which case a default
|
||||
// logger will be used. Optional.
|
||||
Logger *slog.Logger
|
||||
}
|
||||
|
||||
// SubjectTokenProvider can be used to supply a subject token to exchange for a
|
||||
// GCP access token.
|
||||
type SubjectTokenProvider interface {
|
||||
// SubjectToken should return a valid subject token or an error.
|
||||
// The external account token provider does not cache the returned subject
|
||||
// token, so caching logic should be implemented in the provider to prevent
|
||||
// multiple requests for the same subject token.
|
||||
SubjectToken(ctx context.Context, opts *RequestOptions) (string, error)
|
||||
}
|
||||
|
||||
// RequestOptions contains information about the requested subject token or AWS
|
||||
// security credentials from the Google external account credential.
|
||||
type RequestOptions struct {
|
||||
// Audience is the requested audience for the external account credential.
|
||||
Audience string
|
||||
// Subject token type is the requested subject token type for the external
|
||||
// account credential. Expected values include:
|
||||
// “urn:ietf:params:oauth:token-type:jwt”
|
||||
// “urn:ietf:params:oauth:token-type:id-token”
|
||||
// “urn:ietf:params:oauth:token-type:saml2”
|
||||
// “urn:ietf:params:aws:token-type:aws4_request”
|
||||
SubjectTokenType string
|
||||
}
|
||||
|
||||
// AwsSecurityCredentialsProvider can be used to supply AwsSecurityCredentials
|
||||
// and an AWS Region to exchange for a GCP access token.
|
||||
type AwsSecurityCredentialsProvider interface {
|
||||
// AwsRegion should return the AWS region or an error.
|
||||
AwsRegion(ctx context.Context, opts *RequestOptions) (string, error)
|
||||
// GetAwsSecurityCredentials should return a valid set of
|
||||
// AwsSecurityCredentials or an error. The external account token provider
|
||||
// does not cache the returned security credentials, so caching logic should
|
||||
// be implemented in the provider to prevent multiple requests for the
|
||||
// same security credentials.
|
||||
AwsSecurityCredentials(ctx context.Context, opts *RequestOptions) (*AwsSecurityCredentials, error)
|
||||
}
|
||||
|
||||
// AwsSecurityCredentials models AWS security credentials.
|
||||
type AwsSecurityCredentials struct {
|
||||
// AccessKeyId is the AWS Access Key ID - Required.
|
||||
AccessKeyID string `json:"AccessKeyID"`
|
||||
// SecretAccessKey is the AWS Secret Access Key - Required.
|
||||
SecretAccessKey string `json:"SecretAccessKey"`
|
||||
// SessionToken is the AWS Session token. This should be provided for
|
||||
// temporary AWS security credentials - Optional.
|
||||
SessionToken string `json:"Token"`
|
||||
}
|
||||
|
||||
func (o *Options) validate() error {
|
||||
if o.Audience == "" {
|
||||
return fmt.Errorf("externalaccount: Audience must be set")
|
||||
}
|
||||
if o.SubjectTokenType == "" {
|
||||
return fmt.Errorf("externalaccount: Subject token type must be set")
|
||||
}
|
||||
if o.WorkforcePoolUserProject != "" {
|
||||
if valid := validWorkforceAudiencePattern.MatchString(o.Audience); !valid {
|
||||
return fmt.Errorf("externalaccount: workforce_pool_user_project should not be set for non-workforce pool credentials")
|
||||
}
|
||||
}
|
||||
count := 0
|
||||
if o.CredentialSource != nil {
|
||||
count++
|
||||
}
|
||||
if o.SubjectTokenProvider != nil {
|
||||
count++
|
||||
}
|
||||
if o.AwsSecurityCredentialsProvider != nil {
|
||||
count++
|
||||
}
|
||||
if count == 0 {
|
||||
return fmt.Errorf("externalaccount: one of CredentialSource, SubjectTokenProvider, or AwsSecurityCredentialsProvider must be set")
|
||||
}
|
||||
if count > 1 {
|
||||
return fmt.Errorf("externalaccount: only one of CredentialSource, SubjectTokenProvider, or AwsSecurityCredentialsProvider must be set")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// client returns the http client that should be used for the token exchange. If a non-default client
|
||||
// is provided, then the client configured in the options will always be returned. If a default client
|
||||
// is provided and the options are configured for X509 credentials, a new client will be created.
|
||||
func (o *Options) client() (*http.Client, error) {
|
||||
// If a client was provided and no override certificate config location was provided, use the provided client.
|
||||
if o.CredentialSource == nil || o.CredentialSource.Certificate == nil || (!o.IsDefaultClient && o.CredentialSource.Certificate.CertificateConfigLocation == "") {
|
||||
return o.Client, nil
|
||||
}
|
||||
|
||||
// If a new client should be created, validate and use the certificate source to create a new mTLS client.
|
||||
cert := o.CredentialSource.Certificate
|
||||
if !cert.UseDefaultCertificateConfig && cert.CertificateConfigLocation == "" {
|
||||
return nil, errors.New("credentials: \"certificate\" object must either specify a certificate_config_location or use_default_certificate_config should be true")
|
||||
}
|
||||
if cert.UseDefaultCertificateConfig && cert.CertificateConfigLocation != "" {
|
||||
return nil, errors.New("credentials: \"certificate\" object cannot specify both a certificate_config_location and use_default_certificate_config=true")
|
||||
}
|
||||
return createX509Client(cert.CertificateConfigLocation)
|
||||
}
|
||||
|
||||
// resolveTokenURL sets the default STS token endpoint with the configured
|
||||
// universe domain.
|
||||
func (o *Options) resolveTokenURL() {
|
||||
if o.TokenURL != "" {
|
||||
return
|
||||
} else if o.UniverseDomain != "" {
|
||||
o.TokenURL = strings.Replace(defaultTokenURL, universeDomainPlaceholder, o.UniverseDomain, 1)
|
||||
} else {
|
||||
o.TokenURL = strings.Replace(defaultTokenURL, universeDomainPlaceholder, defaultUniverseDomain, 1)
|
||||
}
|
||||
}
|
||||
|
||||
// NewTokenProvider returns a [cloud.google.com/go/auth.TokenProvider]
|
||||
// configured with the provided options.
|
||||
func NewTokenProvider(opts *Options) (auth.TokenProvider, error) {
|
||||
if err := opts.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opts.resolveTokenURL()
|
||||
logger := internallog.New(opts.Logger)
|
||||
stp, err := newSubjectTokenProvider(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client, err := opts.client()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tp := &tokenProvider{
|
||||
client: client,
|
||||
opts: opts,
|
||||
stp: stp,
|
||||
logger: logger,
|
||||
}
|
||||
|
||||
if opts.ServiceAccountImpersonationURL == "" {
|
||||
return auth.NewCachedTokenProvider(tp, nil), nil
|
||||
}
|
||||
|
||||
scopes := make([]string, len(opts.Scopes))
|
||||
copy(scopes, opts.Scopes)
|
||||
// needed for impersonation
|
||||
tp.opts.Scopes = []string{"https://www.googleapis.com/auth/cloud-platform"}
|
||||
imp, err := impersonate.NewTokenProvider(&impersonate.Options{
|
||||
Client: client,
|
||||
URL: opts.ServiceAccountImpersonationURL,
|
||||
Scopes: scopes,
|
||||
Tp: auth.NewCachedTokenProvider(tp, nil),
|
||||
TokenLifetimeSeconds: opts.ServiceAccountImpersonationLifetimeSeconds,
|
||||
Logger: logger,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return auth.NewCachedTokenProvider(imp, nil), nil
|
||||
}
|
||||
|
||||
type subjectTokenProvider interface {
|
||||
subjectToken(ctx context.Context) (string, error)
|
||||
providerType() string
|
||||
}
|
||||
|
||||
// tokenProvider is the provider that handles external credentials. It is used to retrieve Tokens.
|
||||
type tokenProvider struct {
|
||||
client *http.Client
|
||||
logger *slog.Logger
|
||||
opts *Options
|
||||
stp subjectTokenProvider
|
||||
}
|
||||
|
||||
func (tp *tokenProvider) Token(ctx context.Context) (*auth.Token, error) {
|
||||
subjectToken, err := tp.stp.subjectToken(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stsRequest := &stsexchange.TokenRequest{
|
||||
GrantType: stsexchange.GrantType,
|
||||
Audience: tp.opts.Audience,
|
||||
Scope: tp.opts.Scopes,
|
||||
RequestedTokenType: stsexchange.TokenType,
|
||||
SubjectToken: subjectToken,
|
||||
SubjectTokenType: tp.opts.SubjectTokenType,
|
||||
}
|
||||
header := make(http.Header)
|
||||
header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
header.Add("x-goog-api-client", getGoogHeaderValue(tp.opts, tp.stp))
|
||||
clientAuth := stsexchange.ClientAuthentication{
|
||||
AuthStyle: auth.StyleInHeader,
|
||||
ClientID: tp.opts.ClientID,
|
||||
ClientSecret: tp.opts.ClientSecret,
|
||||
}
|
||||
var options map[string]interface{}
|
||||
// Do not pass workforce_pool_user_project when client authentication is used.
|
||||
// The client ID is sufficient for determining the user project.
|
||||
if tp.opts.WorkforcePoolUserProject != "" && tp.opts.ClientID == "" {
|
||||
options = map[string]interface{}{
|
||||
"userProject": tp.opts.WorkforcePoolUserProject,
|
||||
}
|
||||
}
|
||||
stsResp, err := stsexchange.ExchangeToken(ctx, &stsexchange.Options{
|
||||
Client: tp.client,
|
||||
Endpoint: tp.opts.TokenURL,
|
||||
Request: stsRequest,
|
||||
Authentication: clientAuth,
|
||||
Headers: header,
|
||||
ExtraOpts: options,
|
||||
Logger: tp.logger,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tok := &auth.Token{
|
||||
Value: stsResp.AccessToken,
|
||||
Type: stsResp.TokenType,
|
||||
}
|
||||
// The RFC8693 doesn't define the explicit 0 of "expires_in" field behavior.
|
||||
if stsResp.ExpiresIn <= 0 {
|
||||
return nil, fmt.Errorf("credentials: got invalid expiry from security token service")
|
||||
}
|
||||
tok.Expiry = Now().Add(time.Duration(stsResp.ExpiresIn) * time.Second)
|
||||
return tok, nil
|
||||
}
|
||||
|
||||
// newSubjectTokenProvider determines the type of credsfile.CredentialSource needed to create a
|
||||
// subjectTokenProvider
|
||||
func newSubjectTokenProvider(o *Options) (subjectTokenProvider, error) {
|
||||
logger := internallog.New(o.Logger)
|
||||
reqOpts := &RequestOptions{Audience: o.Audience, SubjectTokenType: o.SubjectTokenType}
|
||||
if o.AwsSecurityCredentialsProvider != nil {
|
||||
return &awsSubjectProvider{
|
||||
securityCredentialsProvider: o.AwsSecurityCredentialsProvider,
|
||||
TargetResource: o.Audience,
|
||||
reqOpts: reqOpts,
|
||||
logger: logger,
|
||||
}, nil
|
||||
} else if o.SubjectTokenProvider != nil {
|
||||
return &programmaticProvider{stp: o.SubjectTokenProvider, opts: reqOpts}, nil
|
||||
} else if len(o.CredentialSource.EnvironmentID) > 3 && o.CredentialSource.EnvironmentID[:3] == "aws" {
|
||||
if awsVersion, err := strconv.Atoi(o.CredentialSource.EnvironmentID[3:]); err == nil {
|
||||
if awsVersion != 1 {
|
||||
return nil, fmt.Errorf("credentials: aws version '%d' is not supported in the current build", awsVersion)
|
||||
}
|
||||
|
||||
awsProvider := &awsSubjectProvider{
|
||||
EnvironmentID: o.CredentialSource.EnvironmentID,
|
||||
RegionURL: o.CredentialSource.RegionURL,
|
||||
RegionalCredVerificationURL: o.CredentialSource.RegionalCredVerificationURL,
|
||||
CredVerificationURL: o.CredentialSource.URL,
|
||||
TargetResource: o.Audience,
|
||||
Client: o.Client,
|
||||
logger: logger,
|
||||
}
|
||||
if o.CredentialSource.IMDSv2SessionTokenURL != "" {
|
||||
awsProvider.IMDSv2SessionTokenURL = o.CredentialSource.IMDSv2SessionTokenURL
|
||||
}
|
||||
|
||||
return awsProvider, nil
|
||||
}
|
||||
} else if o.CredentialSource.File != "" {
|
||||
return &fileSubjectProvider{File: o.CredentialSource.File, Format: o.CredentialSource.Format}, nil
|
||||
} else if o.CredentialSource.URL != "" {
|
||||
return &urlSubjectProvider{
|
||||
URL: o.CredentialSource.URL,
|
||||
Headers: o.CredentialSource.Headers,
|
||||
Format: o.CredentialSource.Format,
|
||||
Client: o.Client,
|
||||
Logger: logger,
|
||||
}, nil
|
||||
} else if o.CredentialSource.Executable != nil {
|
||||
ec := o.CredentialSource.Executable
|
||||
if ec.Command == "" {
|
||||
return nil, errors.New("credentials: missing `command` field — executable command must be provided")
|
||||
}
|
||||
|
||||
execProvider := &executableSubjectProvider{}
|
||||
execProvider.Command = ec.Command
|
||||
if ec.TimeoutMillis == 0 {
|
||||
execProvider.Timeout = executableDefaultTimeout
|
||||
} else {
|
||||
execProvider.Timeout = time.Duration(ec.TimeoutMillis) * time.Millisecond
|
||||
if execProvider.Timeout < timeoutMinimum || execProvider.Timeout > timeoutMaximum {
|
||||
return nil, fmt.Errorf("credentials: invalid `timeout_millis` field — executable timeout must be between %v and %v seconds", timeoutMinimum.Seconds(), timeoutMaximum.Seconds())
|
||||
}
|
||||
}
|
||||
execProvider.OutputFile = ec.OutputFile
|
||||
execProvider.client = o.Client
|
||||
execProvider.opts = o
|
||||
execProvider.env = runtimeEnvironment{}
|
||||
return execProvider, nil
|
||||
} else if o.CredentialSource.Certificate != nil {
|
||||
cert := o.CredentialSource.Certificate
|
||||
if !cert.UseDefaultCertificateConfig && cert.CertificateConfigLocation == "" {
|
||||
return nil, errors.New("credentials: \"certificate\" object must either specify a certificate_config_location or use_default_certificate_config should be true")
|
||||
}
|
||||
if cert.UseDefaultCertificateConfig && cert.CertificateConfigLocation != "" {
|
||||
return nil, errors.New("credentials: \"certificate\" object cannot specify both a certificate_config_location and use_default_certificate_config=true")
|
||||
}
|
||||
return &x509Provider{}, nil
|
||||
}
|
||||
return nil, errors.New("credentials: unable to parse credential source")
|
||||
}
|
||||
|
||||
func getGoogHeaderValue(conf *Options, p subjectTokenProvider) string {
|
||||
return fmt.Sprintf("gl-go/%s auth/%s google-byoid-sdk source/%s sa-impersonation/%t config-lifetime/%t",
|
||||
goVersion(),
|
||||
"unknown",
|
||||
p.providerType(),
|
||||
conf.ServiceAccountImpersonationURL != "",
|
||||
conf.ServiceAccountImpersonationLifetimeSeconds != 0)
|
||||
}
|
||||
78
src/server/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/file_provider.go
generated
vendored
Normal file
78
src/server/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/file_provider.go
generated
vendored
Normal file
@ -0,0 +1,78 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package externalaccount
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"cloud.google.com/go/auth/internal"
|
||||
"cloud.google.com/go/auth/internal/credsfile"
|
||||
)
|
||||
|
||||
const (
|
||||
fileProviderType = "file"
|
||||
)
|
||||
|
||||
type fileSubjectProvider struct {
|
||||
File string
|
||||
Format *credsfile.Format
|
||||
}
|
||||
|
||||
func (sp *fileSubjectProvider) subjectToken(context.Context) (string, error) {
|
||||
tokenFile, err := os.Open(sp.File)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("credentials: failed to open credential file %q: %w", sp.File, err)
|
||||
}
|
||||
defer tokenFile.Close()
|
||||
tokenBytes, err := internal.ReadAll(tokenFile)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("credentials: failed to read credential file: %w", err)
|
||||
}
|
||||
tokenBytes = bytes.TrimSpace(tokenBytes)
|
||||
|
||||
if sp.Format == nil {
|
||||
return string(tokenBytes), nil
|
||||
}
|
||||
switch sp.Format.Type {
|
||||
case fileTypeJSON:
|
||||
jsonData := make(map[string]interface{})
|
||||
err = json.Unmarshal(tokenBytes, &jsonData)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("credentials: failed to unmarshal subject token file: %w", err)
|
||||
}
|
||||
val, ok := jsonData[sp.Format.SubjectTokenFieldName]
|
||||
if !ok {
|
||||
return "", errors.New("credentials: provided subject_token_field_name not found in credentials")
|
||||
}
|
||||
token, ok := val.(string)
|
||||
if !ok {
|
||||
return "", errors.New("credentials: improperly formatted subject token")
|
||||
}
|
||||
return token, nil
|
||||
case fileTypeText:
|
||||
return string(tokenBytes), nil
|
||||
default:
|
||||
return "", errors.New("credentials: invalid credential_source file format type: " + sp.Format.Type)
|
||||
}
|
||||
}
|
||||
|
||||
func (sp *fileSubjectProvider) providerType() string {
|
||||
return fileProviderType
|
||||
}
|
||||
74
src/server/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/info.go
generated
vendored
Normal file
74
src/server/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/info.go
generated
vendored
Normal file
@ -0,0 +1,74 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package externalaccount
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
var (
|
||||
// version is a package internal global variable for testing purposes.
|
||||
version = runtime.Version
|
||||
)
|
||||
|
||||
// versionUnknown is only used when the runtime version cannot be determined.
|
||||
const versionUnknown = "UNKNOWN"
|
||||
|
||||
// goVersion returns a Go runtime version derived from the runtime environment
|
||||
// that is modified to be suitable for reporting in a header, meaning it has no
|
||||
// whitespace. If it is unable to determine the Go runtime version, it returns
|
||||
// versionUnknown.
|
||||
func goVersion() string {
|
||||
const develPrefix = "devel +"
|
||||
|
||||
s := version()
|
||||
if strings.HasPrefix(s, develPrefix) {
|
||||
s = s[len(develPrefix):]
|
||||
if p := strings.IndexFunc(s, unicode.IsSpace); p >= 0 {
|
||||
s = s[:p]
|
||||
}
|
||||
return s
|
||||
} else if p := strings.IndexFunc(s, unicode.IsSpace); p >= 0 {
|
||||
s = s[:p]
|
||||
}
|
||||
|
||||
notSemverRune := func(r rune) bool {
|
||||
return !strings.ContainsRune("0123456789.", r)
|
||||
}
|
||||
|
||||
if strings.HasPrefix(s, "go1") {
|
||||
s = s[2:]
|
||||
var prerelease string
|
||||
if p := strings.IndexFunc(s, notSemverRune); p >= 0 {
|
||||
s, prerelease = s[:p], s[p:]
|
||||
}
|
||||
if strings.HasSuffix(s, ".") {
|
||||
s += "0"
|
||||
} else if strings.Count(s, ".") < 2 {
|
||||
s += ".0"
|
||||
}
|
||||
if prerelease != "" {
|
||||
// Some release candidates already have a dash in them.
|
||||
if !strings.HasPrefix(prerelease, "-") {
|
||||
prerelease = "-" + prerelease
|
||||
}
|
||||
s += prerelease
|
||||
}
|
||||
return s
|
||||
}
|
||||
return versionUnknown
|
||||
}
|
||||
30
src/server/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/programmatic_provider.go
generated
vendored
Normal file
30
src/server/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/programmatic_provider.go
generated
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package externalaccount
|
||||
|
||||
import "context"
|
||||
|
||||
type programmaticProvider struct {
|
||||
opts *RequestOptions
|
||||
stp SubjectTokenProvider
|
||||
}
|
||||
|
||||
func (pp *programmaticProvider) providerType() string {
|
||||
return programmaticProviderType
|
||||
}
|
||||
|
||||
func (pp *programmaticProvider) subjectToken(ctx context.Context) (string, error) {
|
||||
return pp.stp.SubjectToken(ctx, pp.opts)
|
||||
}
|
||||
93
src/server/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/url_provider.go
generated
vendored
Normal file
93
src/server/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/url_provider.go
generated
vendored
Normal file
@ -0,0 +1,93 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package externalaccount
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"cloud.google.com/go/auth/internal"
|
||||
"cloud.google.com/go/auth/internal/credsfile"
|
||||
"github.com/googleapis/gax-go/v2/internallog"
|
||||
)
|
||||
|
||||
const (
|
||||
fileTypeText = "text"
|
||||
fileTypeJSON = "json"
|
||||
urlProviderType = "url"
|
||||
programmaticProviderType = "programmatic"
|
||||
x509ProviderType = "x509"
|
||||
)
|
||||
|
||||
type urlSubjectProvider struct {
|
||||
URL string
|
||||
Headers map[string]string
|
||||
Format *credsfile.Format
|
||||
Client *http.Client
|
||||
Logger *slog.Logger
|
||||
}
|
||||
|
||||
func (sp *urlSubjectProvider) subjectToken(ctx context.Context) (string, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", sp.URL, nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("credentials: HTTP request for URL-sourced credential failed: %w", err)
|
||||
}
|
||||
|
||||
for key, val := range sp.Headers {
|
||||
req.Header.Add(key, val)
|
||||
}
|
||||
sp.Logger.DebugContext(ctx, "url subject token request", "request", internallog.HTTPRequest(req, nil))
|
||||
resp, body, err := internal.DoRequest(sp.Client, req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("credentials: invalid response when retrieving subject token: %w", err)
|
||||
}
|
||||
sp.Logger.DebugContext(ctx, "url subject token response", "response", internallog.HTTPResponse(resp, body))
|
||||
if c := resp.StatusCode; c < http.StatusOK || c >= http.StatusMultipleChoices {
|
||||
return "", fmt.Errorf("credentials: status code %d: %s", c, body)
|
||||
}
|
||||
|
||||
if sp.Format == nil {
|
||||
return string(body), nil
|
||||
}
|
||||
switch sp.Format.Type {
|
||||
case "json":
|
||||
jsonData := make(map[string]interface{})
|
||||
err = json.Unmarshal(body, &jsonData)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("credentials: failed to unmarshal subject token file: %w", err)
|
||||
}
|
||||
val, ok := jsonData[sp.Format.SubjectTokenFieldName]
|
||||
if !ok {
|
||||
return "", errors.New("credentials: provided subject_token_field_name not found in credentials")
|
||||
}
|
||||
token, ok := val.(string)
|
||||
if !ok {
|
||||
return "", errors.New("credentials: improperly formatted subject token")
|
||||
}
|
||||
return token, nil
|
||||
case fileTypeText:
|
||||
return string(body), nil
|
||||
default:
|
||||
return "", errors.New("credentials: invalid credential_source file format type: " + sp.Format.Type)
|
||||
}
|
||||
}
|
||||
|
||||
func (sp *urlSubjectProvider) providerType() string {
|
||||
return urlProviderType
|
||||
}
|
||||
63
src/server/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/x509_provider.go
generated
vendored
Normal file
63
src/server/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/x509_provider.go
generated
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package externalaccount
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/auth/internal/transport/cert"
|
||||
)
|
||||
|
||||
// x509Provider implements the subjectTokenProvider type for
|
||||
// x509 workload identity credentials. Because x509 credentials
|
||||
// rely on an mTLS connection to represent the 3rd party identity
|
||||
// rather than a subject token, this provider will always return
|
||||
// an empty string when a subject token is requested by the external account
|
||||
// token provider.
|
||||
type x509Provider struct {
|
||||
}
|
||||
|
||||
func (xp *x509Provider) providerType() string {
|
||||
return x509ProviderType
|
||||
}
|
||||
|
||||
func (xp *x509Provider) subjectToken(ctx context.Context) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// createX509Client creates a new client that is configured with mTLS, using the
|
||||
// certificate configuration specified in the credential source.
|
||||
func createX509Client(certificateConfigLocation string) (*http.Client, error) {
|
||||
certProvider, err := cert.NewWorkloadX509CertProvider(certificateConfigLocation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
trans := http.DefaultTransport.(*http.Transport).Clone()
|
||||
|
||||
trans.TLSClientConfig = &tls.Config{
|
||||
GetClientCertificate: certProvider,
|
||||
}
|
||||
|
||||
// Create a client with default settings plus the X509 workload cert and key.
|
||||
client := &http.Client{
|
||||
Transport: trans,
|
||||
Timeout: 30 * time.Second,
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
115
src/server/vendor/cloud.google.com/go/auth/credentials/internal/externalaccountuser/externalaccountuser.go
generated
vendored
Normal file
115
src/server/vendor/cloud.google.com/go/auth/credentials/internal/externalaccountuser/externalaccountuser.go
generated
vendored
Normal file
@ -0,0 +1,115 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package externalaccountuser
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/auth"
|
||||
"cloud.google.com/go/auth/credentials/internal/stsexchange"
|
||||
"cloud.google.com/go/auth/internal"
|
||||
"github.com/googleapis/gax-go/v2/internallog"
|
||||
)
|
||||
|
||||
// Options stores the configuration for fetching tokens with external authorized
|
||||
// user credentials.
|
||||
type Options struct {
|
||||
// Audience is the Secure Token Service (STS) audience which contains the
|
||||
// resource name for the workforce pool and the provider identifier in that
|
||||
// pool.
|
||||
Audience string
|
||||
// RefreshToken is the OAuth 2.0 refresh token.
|
||||
RefreshToken string
|
||||
// TokenURL is the STS token exchange endpoint for refresh.
|
||||
TokenURL string
|
||||
// TokenInfoURL is the STS endpoint URL for token introspection. Optional.
|
||||
TokenInfoURL string
|
||||
// ClientID is only required in conjunction with ClientSecret, as described
|
||||
// below.
|
||||
ClientID string
|
||||
// ClientSecret is currently only required if token_info endpoint also needs
|
||||
// to be called with the generated a cloud access token. When provided, STS
|
||||
// will be called with additional basic authentication using client_id as
|
||||
// username and client_secret as password.
|
||||
ClientSecret string
|
||||
// Scopes contains the desired scopes for the returned access token.
|
||||
Scopes []string
|
||||
|
||||
// Client for token request.
|
||||
Client *http.Client
|
||||
// Logger for logging.
|
||||
Logger *slog.Logger
|
||||
}
|
||||
|
||||
func (c *Options) validate() bool {
|
||||
return c.ClientID != "" && c.ClientSecret != "" && c.RefreshToken != "" && c.TokenURL != ""
|
||||
}
|
||||
|
||||
// NewTokenProvider returns a [cloud.google.com/go/auth.TokenProvider]
|
||||
// configured with the provided options.
|
||||
func NewTokenProvider(opts *Options) (auth.TokenProvider, error) {
|
||||
if !opts.validate() {
|
||||
return nil, errors.New("credentials: invalid external_account_authorized_user configuration")
|
||||
}
|
||||
|
||||
tp := &tokenProvider{
|
||||
o: opts,
|
||||
}
|
||||
return auth.NewCachedTokenProvider(tp, nil), nil
|
||||
}
|
||||
|
||||
type tokenProvider struct {
|
||||
o *Options
|
||||
}
|
||||
|
||||
func (tp *tokenProvider) Token(ctx context.Context) (*auth.Token, error) {
|
||||
opts := tp.o
|
||||
|
||||
clientAuth := stsexchange.ClientAuthentication{
|
||||
AuthStyle: auth.StyleInHeader,
|
||||
ClientID: opts.ClientID,
|
||||
ClientSecret: opts.ClientSecret,
|
||||
}
|
||||
headers := make(http.Header)
|
||||
headers.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
stsResponse, err := stsexchange.RefreshAccessToken(ctx, &stsexchange.Options{
|
||||
Client: opts.Client,
|
||||
Endpoint: opts.TokenURL,
|
||||
RefreshToken: opts.RefreshToken,
|
||||
Authentication: clientAuth,
|
||||
Headers: headers,
|
||||
Logger: internallog.New(tp.o.Logger),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if stsResponse.ExpiresIn < 0 {
|
||||
return nil, errors.New("credentials: invalid expiry from security token service")
|
||||
}
|
||||
|
||||
// guarded by the wrapping with CachedTokenProvider
|
||||
if stsResponse.RefreshToken != "" {
|
||||
opts.RefreshToken = stsResponse.RefreshToken
|
||||
}
|
||||
return &auth.Token{
|
||||
Value: stsResponse.AccessToken,
|
||||
Expiry: time.Now().UTC().Add(time.Duration(stsResponse.ExpiresIn) * time.Second),
|
||||
Type: internal.TokenTypeBearer,
|
||||
}, nil
|
||||
}
|
||||
191
src/server/vendor/cloud.google.com/go/auth/credentials/internal/gdch/gdch.go
generated
vendored
Normal file
191
src/server/vendor/cloud.google.com/go/auth/credentials/internal/gdch/gdch.go
generated
vendored
Normal file
@ -0,0 +1,191 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package gdch
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/auth"
|
||||
"cloud.google.com/go/auth/internal"
|
||||
"cloud.google.com/go/auth/internal/credsfile"
|
||||
"cloud.google.com/go/auth/internal/jwt"
|
||||
"github.com/googleapis/gax-go/v2/internallog"
|
||||
)
|
||||
|
||||
const (
|
||||
// GrantType is the grant type for the token request.
|
||||
GrantType = "urn:ietf:params:oauth:token-type:token-exchange"
|
||||
requestTokenType = "urn:ietf:params:oauth:token-type:access_token"
|
||||
subjectTokenType = "urn:k8s:params:oauth:token-type:serviceaccount"
|
||||
)
|
||||
|
||||
var (
|
||||
gdchSupportFormatVersions map[string]bool = map[string]bool{
|
||||
"1": true,
|
||||
}
|
||||
)
|
||||
|
||||
// Options for [NewTokenProvider].
|
||||
type Options struct {
|
||||
STSAudience string
|
||||
Client *http.Client
|
||||
Logger *slog.Logger
|
||||
}
|
||||
|
||||
// NewTokenProvider returns a [cloud.google.com/go/auth.TokenProvider] from a
|
||||
// GDCH cred file.
|
||||
func NewTokenProvider(f *credsfile.GDCHServiceAccountFile, o *Options) (auth.TokenProvider, error) {
|
||||
if !gdchSupportFormatVersions[f.FormatVersion] {
|
||||
return nil, fmt.Errorf("credentials: unsupported gdch_service_account format %q", f.FormatVersion)
|
||||
}
|
||||
if o.STSAudience == "" {
|
||||
return nil, errors.New("credentials: STSAudience must be set for the GDCH auth flows")
|
||||
}
|
||||
signer, err := internal.ParseKey([]byte(f.PrivateKey))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certPool, err := loadCertPool(f.CertPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tp := gdchProvider{
|
||||
serviceIdentity: fmt.Sprintf("system:serviceaccount:%s:%s", f.Project, f.Name),
|
||||
tokenURL: f.TokenURL,
|
||||
aud: o.STSAudience,
|
||||
signer: signer,
|
||||
pkID: f.PrivateKeyID,
|
||||
certPool: certPool,
|
||||
client: o.Client,
|
||||
logger: internallog.New(o.Logger),
|
||||
}
|
||||
return tp, nil
|
||||
}
|
||||
|
||||
func loadCertPool(path string) (*x509.CertPool, error) {
|
||||
pool := x509.NewCertPool()
|
||||
pem, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("credentials: failed to read certificate: %w", err)
|
||||
}
|
||||
pool.AppendCertsFromPEM(pem)
|
||||
return pool, nil
|
||||
}
|
||||
|
||||
type gdchProvider struct {
|
||||
serviceIdentity string
|
||||
tokenURL string
|
||||
aud string
|
||||
signer crypto.Signer
|
||||
pkID string
|
||||
certPool *x509.CertPool
|
||||
|
||||
client *http.Client
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
func (g gdchProvider) Token(ctx context.Context) (*auth.Token, error) {
|
||||
addCertToTransport(g.client, g.certPool)
|
||||
iat := time.Now()
|
||||
exp := iat.Add(time.Hour)
|
||||
claims := jwt.Claims{
|
||||
Iss: g.serviceIdentity,
|
||||
Sub: g.serviceIdentity,
|
||||
Aud: g.tokenURL,
|
||||
Iat: iat.Unix(),
|
||||
Exp: exp.Unix(),
|
||||
}
|
||||
h := jwt.Header{
|
||||
Algorithm: jwt.HeaderAlgRSA256,
|
||||
Type: jwt.HeaderType,
|
||||
KeyID: string(g.pkID),
|
||||
}
|
||||
payload, err := jwt.EncodeJWS(&h, &claims, g.signer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v := url.Values{}
|
||||
v.Set("grant_type", GrantType)
|
||||
v.Set("audience", g.aud)
|
||||
v.Set("requested_token_type", requestTokenType)
|
||||
v.Set("subject_token", payload)
|
||||
v.Set("subject_token_type", subjectTokenType)
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", g.tokenURL, strings.NewReader(v.Encode()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
g.logger.DebugContext(ctx, "gdch token request", "request", internallog.HTTPRequest(req, []byte(v.Encode())))
|
||||
resp, body, err := internal.DoRequest(g.client, req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("credentials: cannot fetch token: %w", err)
|
||||
}
|
||||
g.logger.DebugContext(ctx, "gdch token response", "response", internallog.HTTPResponse(resp, body))
|
||||
if c := resp.StatusCode; c < http.StatusOK || c > http.StatusMultipleChoices {
|
||||
return nil, &auth.Error{
|
||||
Response: resp,
|
||||
Body: body,
|
||||
}
|
||||
}
|
||||
|
||||
var tokenRes struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
TokenType string `json:"token_type"`
|
||||
ExpiresIn int64 `json:"expires_in"` // relative seconds from now
|
||||
}
|
||||
if err := json.Unmarshal(body, &tokenRes); err != nil {
|
||||
return nil, fmt.Errorf("credentials: cannot fetch token: %w", err)
|
||||
}
|
||||
token := &auth.Token{
|
||||
Value: tokenRes.AccessToken,
|
||||
Type: tokenRes.TokenType,
|
||||
}
|
||||
raw := make(map[string]interface{})
|
||||
json.Unmarshal(body, &raw) // no error checks for optional fields
|
||||
token.Metadata = raw
|
||||
|
||||
if secs := tokenRes.ExpiresIn; secs > 0 {
|
||||
token.Expiry = time.Now().Add(time.Duration(secs) * time.Second)
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
|
||||
// addCertToTransport makes a best effort attempt at adding in the cert info to
|
||||
// the client. It tries to keep all configured transport settings if the
|
||||
// underlying transport is an http.Transport. Or else it overwrites the
|
||||
// transport with defaults adding in the certs.
|
||||
func addCertToTransport(hc *http.Client, certPool *x509.CertPool) {
|
||||
trans, ok := hc.Transport.(*http.Transport)
|
||||
if !ok {
|
||||
trans = http.DefaultTransport.(*http.Transport).Clone()
|
||||
}
|
||||
trans.TLSClientConfig = &tls.Config{
|
||||
RootCAs: certPool,
|
||||
}
|
||||
}
|
||||
105
src/server/vendor/cloud.google.com/go/auth/credentials/internal/impersonate/idtoken.go
generated
vendored
Normal file
105
src/server/vendor/cloud.google.com/go/auth/credentials/internal/impersonate/idtoken.go
generated
vendored
Normal file
@ -0,0 +1,105 @@
|
||||
// Copyright 2025 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package impersonate
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/auth"
|
||||
"cloud.google.com/go/auth/internal"
|
||||
"github.com/googleapis/gax-go/v2/internallog"
|
||||
)
|
||||
|
||||
var (
|
||||
universeDomainPlaceholder = "UNIVERSE_DOMAIN"
|
||||
iamCredentialsUniverseDomainEndpoint = "https://iamcredentials.UNIVERSE_DOMAIN"
|
||||
)
|
||||
|
||||
// IDTokenIAMOptions provides configuration for [IDTokenIAMOptions.Token].
|
||||
type IDTokenIAMOptions struct {
|
||||
// Client is required.
|
||||
Client *http.Client
|
||||
// Logger is required.
|
||||
Logger *slog.Logger
|
||||
UniverseDomain auth.CredentialsPropertyProvider
|
||||
ServiceAccountEmail string
|
||||
GenerateIDTokenRequest
|
||||
}
|
||||
|
||||
// GenerateIDTokenRequest holds the request to the IAM generateIdToken RPC.
|
||||
type GenerateIDTokenRequest struct {
|
||||
Audience string `json:"audience"`
|
||||
IncludeEmail bool `json:"includeEmail"`
|
||||
// Delegates are the ordered, fully-qualified resource name for service
|
||||
// accounts in a delegation chain. Each service account must be granted
|
||||
// roles/iam.serviceAccountTokenCreator on the next service account in the
|
||||
// chain. The delegates must have the following format:
|
||||
// projects/-/serviceAccounts/{ACCOUNT_EMAIL_OR_UNIQUEID}. The - wildcard
|
||||
// character is required; replacing it with a project ID is invalid.
|
||||
// Optional.
|
||||
Delegates []string `json:"delegates,omitempty"`
|
||||
}
|
||||
|
||||
// GenerateIDTokenResponse holds the response from the IAM generateIdToken RPC.
|
||||
type GenerateIDTokenResponse struct {
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
// Token call IAM generateIdToken with the configuration provided in [IDTokenIAMOptions].
|
||||
func (o IDTokenIAMOptions) Token(ctx context.Context) (*auth.Token, error) {
|
||||
universeDomain, err := o.UniverseDomain.GetProperty(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
endpoint := strings.Replace(iamCredentialsUniverseDomainEndpoint, universeDomainPlaceholder, universeDomain, 1)
|
||||
url := fmt.Sprintf("%s/v1/%s:generateIdToken", endpoint, internal.FormatIAMServiceAccountResource(o.ServiceAccountEmail))
|
||||
|
||||
bodyBytes, err := json.Marshal(o.GenerateIDTokenRequest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("impersonate: unable to marshal request: %w", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(bodyBytes))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("impersonate: unable to create request: %w", err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
o.Logger.DebugContext(ctx, "impersonated idtoken request", "request", internallog.HTTPRequest(req, bodyBytes))
|
||||
resp, body, err := internal.DoRequest(o.Client, req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("impersonate: unable to generate ID token: %w", err)
|
||||
}
|
||||
o.Logger.DebugContext(ctx, "impersonated idtoken response", "response", internallog.HTTPResponse(resp, body))
|
||||
if c := resp.StatusCode; c < 200 || c > 299 {
|
||||
return nil, fmt.Errorf("impersonate: status code %d: %s", c, body)
|
||||
}
|
||||
|
||||
var tokenResp GenerateIDTokenResponse
|
||||
if err := json.Unmarshal(body, &tokenResp); err != nil {
|
||||
return nil, fmt.Errorf("impersonate: unable to parse response: %w", err)
|
||||
}
|
||||
return &auth.Token{
|
||||
Value: tokenResp.Token,
|
||||
// Generated ID tokens are good for one hour.
|
||||
Expiry: time.Now().Add(1 * time.Hour),
|
||||
}, nil
|
||||
}
|
||||
156
src/server/vendor/cloud.google.com/go/auth/credentials/internal/impersonate/impersonate.go
generated
vendored
Normal file
156
src/server/vendor/cloud.google.com/go/auth/credentials/internal/impersonate/impersonate.go
generated
vendored
Normal file
@ -0,0 +1,156 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package impersonate
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/auth"
|
||||
"cloud.google.com/go/auth/internal"
|
||||
"github.com/googleapis/gax-go/v2/internallog"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultTokenLifetime = "3600s"
|
||||
authHeaderKey = "Authorization"
|
||||
)
|
||||
|
||||
// generateAccesstokenReq is used for service account impersonation
|
||||
type generateAccessTokenReq struct {
|
||||
Delegates []string `json:"delegates,omitempty"`
|
||||
Lifetime string `json:"lifetime,omitempty"`
|
||||
Scope []string `json:"scope,omitempty"`
|
||||
}
|
||||
|
||||
type impersonateTokenResponse struct {
|
||||
AccessToken string `json:"accessToken"`
|
||||
ExpireTime string `json:"expireTime"`
|
||||
}
|
||||
|
||||
// NewTokenProvider uses a source credential, stored in Ts, to request an access token to the provided URL.
|
||||
// Scopes can be defined when the access token is requested.
|
||||
func NewTokenProvider(opts *Options) (auth.TokenProvider, error) {
|
||||
if err := opts.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return opts, nil
|
||||
}
|
||||
|
||||
// Options for [NewTokenProvider].
|
||||
type Options struct {
|
||||
// Tp is the source credential used to generate a token on the
|
||||
// impersonated service account. Required.
|
||||
Tp auth.TokenProvider
|
||||
|
||||
// URL is the endpoint to call to generate a token
|
||||
// on behalf of the service account. Required.
|
||||
URL string
|
||||
// Scopes that the impersonated credential should have. Required.
|
||||
Scopes []string
|
||||
// Delegates are the service account email addresses in a delegation chain.
|
||||
// Each service account must be granted roles/iam.serviceAccountTokenCreator
|
||||
// on the next service account in the chain. Optional.
|
||||
Delegates []string
|
||||
// TokenLifetimeSeconds is the number of seconds the impersonation token will
|
||||
// be valid for. Defaults to 1 hour if unset. Optional.
|
||||
TokenLifetimeSeconds int
|
||||
// Client configures the underlying client used to make network requests
|
||||
// when fetching tokens. Required.
|
||||
Client *http.Client
|
||||
// Logger is used for debug logging. If provided, logging will be enabled
|
||||
// at the loggers configured level. By default logging is disabled unless
|
||||
// enabled by setting GOOGLE_SDK_GO_LOGGING_LEVEL in which case a default
|
||||
// logger will be used. Optional.
|
||||
Logger *slog.Logger
|
||||
}
|
||||
|
||||
func (o *Options) validate() error {
|
||||
if o.Tp == nil {
|
||||
return errors.New("credentials: missing required 'source_credentials' field in impersonated credentials")
|
||||
}
|
||||
if o.URL == "" {
|
||||
return errors.New("credentials: missing required 'service_account_impersonation_url' field in impersonated credentials")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Token performs the exchange to get a temporary service account token to allow access to GCP.
|
||||
func (o *Options) Token(ctx context.Context) (*auth.Token, error) {
|
||||
logger := internallog.New(o.Logger)
|
||||
lifetime := defaultTokenLifetime
|
||||
if o.TokenLifetimeSeconds != 0 {
|
||||
lifetime = fmt.Sprintf("%ds", o.TokenLifetimeSeconds)
|
||||
}
|
||||
reqBody := generateAccessTokenReq{
|
||||
Lifetime: lifetime,
|
||||
Scope: o.Scopes,
|
||||
Delegates: o.Delegates,
|
||||
}
|
||||
b, err := json.Marshal(reqBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("credentials: unable to marshal request: %w", err)
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", o.URL, bytes.NewReader(b))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("credentials: unable to create impersonation request: %w", err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
if err := setAuthHeader(ctx, o.Tp, req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logger.DebugContext(ctx, "impersonated token request", "request", internallog.HTTPRequest(req, b))
|
||||
resp, body, err := internal.DoRequest(o.Client, req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("credentials: unable to generate access token: %w", err)
|
||||
}
|
||||
logger.DebugContext(ctx, "impersonated token response", "response", internallog.HTTPResponse(resp, body))
|
||||
if c := resp.StatusCode; c < http.StatusOK || c >= http.StatusMultipleChoices {
|
||||
return nil, fmt.Errorf("credentials: status code %d: %s", c, body)
|
||||
}
|
||||
|
||||
var accessTokenResp impersonateTokenResponse
|
||||
if err := json.Unmarshal(body, &accessTokenResp); err != nil {
|
||||
return nil, fmt.Errorf("credentials: unable to parse response: %w", err)
|
||||
}
|
||||
expiry, err := time.Parse(time.RFC3339, accessTokenResp.ExpireTime)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("credentials: unable to parse expiry: %w", err)
|
||||
}
|
||||
return &auth.Token{
|
||||
Value: accessTokenResp.AccessToken,
|
||||
Expiry: expiry,
|
||||
Type: internal.TokenTypeBearer,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func setAuthHeader(ctx context.Context, tp auth.TokenProvider, r *http.Request) error {
|
||||
t, err := tp.Token(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
typ := t.Type
|
||||
if typ == "" {
|
||||
typ = internal.TokenTypeBearer
|
||||
}
|
||||
r.Header.Set(authHeaderKey, typ+" "+t.Value)
|
||||
return nil
|
||||
}
|
||||
167
src/server/vendor/cloud.google.com/go/auth/credentials/internal/stsexchange/sts_exchange.go
generated
vendored
Normal file
167
src/server/vendor/cloud.google.com/go/auth/credentials/internal/stsexchange/sts_exchange.go
generated
vendored
Normal file
@ -0,0 +1,167 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package stsexchange
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"cloud.google.com/go/auth"
|
||||
"cloud.google.com/go/auth/internal"
|
||||
"github.com/googleapis/gax-go/v2/internallog"
|
||||
)
|
||||
|
||||
const (
|
||||
// GrantType for a sts exchange.
|
||||
GrantType = "urn:ietf:params:oauth:grant-type:token-exchange"
|
||||
// TokenType for a sts exchange.
|
||||
TokenType = "urn:ietf:params:oauth:token-type:access_token"
|
||||
|
||||
jwtTokenType = "urn:ietf:params:oauth:token-type:jwt"
|
||||
)
|
||||
|
||||
// Options stores the configuration for making an sts exchange request.
|
||||
type Options struct {
|
||||
Client *http.Client
|
||||
Logger *slog.Logger
|
||||
Endpoint string
|
||||
Request *TokenRequest
|
||||
Authentication ClientAuthentication
|
||||
Headers http.Header
|
||||
// ExtraOpts are optional fields marshalled into the `options` field of the
|
||||
// request body.
|
||||
ExtraOpts map[string]interface{}
|
||||
RefreshToken string
|
||||
}
|
||||
|
||||
// RefreshAccessToken performs the token exchange using a refresh token flow.
|
||||
func RefreshAccessToken(ctx context.Context, opts *Options) (*TokenResponse, error) {
|
||||
data := url.Values{}
|
||||
data.Set("grant_type", "refresh_token")
|
||||
data.Set("refresh_token", opts.RefreshToken)
|
||||
return doRequest(ctx, opts, data)
|
||||
}
|
||||
|
||||
// ExchangeToken performs an oauth2 token exchange with the provided endpoint.
|
||||
func ExchangeToken(ctx context.Context, opts *Options) (*TokenResponse, error) {
|
||||
data := url.Values{}
|
||||
data.Set("audience", opts.Request.Audience)
|
||||
data.Set("grant_type", GrantType)
|
||||
data.Set("requested_token_type", TokenType)
|
||||
data.Set("subject_token_type", opts.Request.SubjectTokenType)
|
||||
data.Set("subject_token", opts.Request.SubjectToken)
|
||||
data.Set("scope", strings.Join(opts.Request.Scope, " "))
|
||||
if opts.ExtraOpts != nil {
|
||||
opts, err := json.Marshal(opts.ExtraOpts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("credentials: failed to marshal additional options: %w", err)
|
||||
}
|
||||
data.Set("options", string(opts))
|
||||
}
|
||||
return doRequest(ctx, opts, data)
|
||||
}
|
||||
|
||||
func doRequest(ctx context.Context, opts *Options, data url.Values) (*TokenResponse, error) {
|
||||
opts.Authentication.InjectAuthentication(data, opts.Headers)
|
||||
encodedData := data.Encode()
|
||||
logger := internallog.New(opts.Logger)
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", opts.Endpoint, strings.NewReader(encodedData))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("credentials: failed to properly build http request: %w", err)
|
||||
|
||||
}
|
||||
for key, list := range opts.Headers {
|
||||
for _, val := range list {
|
||||
req.Header.Add(key, val)
|
||||
}
|
||||
}
|
||||
req.Header.Set("Content-Length", strconv.Itoa(len(encodedData)))
|
||||
|
||||
logger.DebugContext(ctx, "sts token request", "request", internallog.HTTPRequest(req, []byte(encodedData)))
|
||||
resp, body, err := internal.DoRequest(opts.Client, req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("credentials: invalid response from Secure Token Server: %w", err)
|
||||
}
|
||||
logger.DebugContext(ctx, "sts token response", "response", internallog.HTTPResponse(resp, body))
|
||||
if c := resp.StatusCode; c < http.StatusOK || c > http.StatusMultipleChoices {
|
||||
return nil, fmt.Errorf("credentials: status code %d: %s", c, body)
|
||||
}
|
||||
var stsResp TokenResponse
|
||||
if err := json.Unmarshal(body, &stsResp); err != nil {
|
||||
return nil, fmt.Errorf("credentials: failed to unmarshal response body from Secure Token Server: %w", err)
|
||||
}
|
||||
|
||||
return &stsResp, nil
|
||||
}
|
||||
|
||||
// TokenRequest contains fields necessary to make an oauth2 token
|
||||
// exchange.
|
||||
type TokenRequest struct {
|
||||
ActingParty struct {
|
||||
ActorToken string
|
||||
ActorTokenType string
|
||||
}
|
||||
GrantType string
|
||||
Resource string
|
||||
Audience string
|
||||
Scope []string
|
||||
RequestedTokenType string
|
||||
SubjectToken string
|
||||
SubjectTokenType string
|
||||
}
|
||||
|
||||
// TokenResponse is used to decode the remote server response during
|
||||
// an oauth2 token exchange.
|
||||
type TokenResponse struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
IssuedTokenType string `json:"issued_token_type"`
|
||||
TokenType string `json:"token_type"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
Scope string `json:"scope"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
}
|
||||
|
||||
// ClientAuthentication represents an OAuth client ID and secret and the
|
||||
// mechanism for passing these credentials as stated in rfc6749#2.3.1.
|
||||
type ClientAuthentication struct {
|
||||
AuthStyle auth.Style
|
||||
ClientID string
|
||||
ClientSecret string
|
||||
}
|
||||
|
||||
// InjectAuthentication is used to add authentication to a Secure Token Service
|
||||
// exchange request. It modifies either the passed url.Values or http.Header
|
||||
// depending on the desired authentication format.
|
||||
func (c *ClientAuthentication) InjectAuthentication(values url.Values, headers http.Header) {
|
||||
if c.ClientID == "" || c.ClientSecret == "" || values == nil || headers == nil {
|
||||
return
|
||||
}
|
||||
switch c.AuthStyle {
|
||||
case auth.StyleInHeader:
|
||||
plainHeader := c.ClientID + ":" + c.ClientSecret
|
||||
headers.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(plainHeader)))
|
||||
default:
|
||||
values.Set("client_id", c.ClientID)
|
||||
values.Set("client_secret", c.ClientSecret)
|
||||
}
|
||||
}
|
||||
89
src/server/vendor/cloud.google.com/go/auth/credentials/selfsignedjwt.go
generated
vendored
Normal file
89
src/server/vendor/cloud.google.com/go/auth/credentials/selfsignedjwt.go
generated
vendored
Normal file
@ -0,0 +1,89 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package credentials
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/auth"
|
||||
"cloud.google.com/go/auth/internal"
|
||||
"cloud.google.com/go/auth/internal/credsfile"
|
||||
"cloud.google.com/go/auth/internal/jwt"
|
||||
)
|
||||
|
||||
var (
|
||||
// for testing
|
||||
now func() time.Time = time.Now
|
||||
)
|
||||
|
||||
// configureSelfSignedJWT uses the private key in the service account to create
|
||||
// a JWT without making a network call.
|
||||
func configureSelfSignedJWT(f *credsfile.ServiceAccountFile, opts *DetectOptions) (auth.TokenProvider, error) {
|
||||
if len(opts.scopes()) == 0 && opts.Audience == "" {
|
||||
return nil, errors.New("credentials: both scopes and audience are empty")
|
||||
}
|
||||
signer, err := internal.ParseKey([]byte(f.PrivateKey))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("credentials: could not parse key: %w", err)
|
||||
}
|
||||
return &selfSignedTokenProvider{
|
||||
email: f.ClientEmail,
|
||||
audience: opts.Audience,
|
||||
scopes: opts.scopes(),
|
||||
signer: signer,
|
||||
pkID: f.PrivateKeyID,
|
||||
logger: opts.logger(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type selfSignedTokenProvider struct {
|
||||
email string
|
||||
audience string
|
||||
scopes []string
|
||||
signer crypto.Signer
|
||||
pkID string
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
func (tp *selfSignedTokenProvider) Token(context.Context) (*auth.Token, error) {
|
||||
iat := now()
|
||||
exp := iat.Add(time.Hour)
|
||||
scope := strings.Join(tp.scopes, " ")
|
||||
c := &jwt.Claims{
|
||||
Iss: tp.email,
|
||||
Sub: tp.email,
|
||||
Aud: tp.audience,
|
||||
Scope: scope,
|
||||
Iat: iat.Unix(),
|
||||
Exp: exp.Unix(),
|
||||
}
|
||||
h := &jwt.Header{
|
||||
Algorithm: jwt.HeaderAlgRSA256,
|
||||
Type: jwt.HeaderType,
|
||||
KeyID: string(tp.pkID),
|
||||
}
|
||||
tok, err := jwt.EncodeJWS(h, c, tp.signer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("credentials: could not encode JWT: %w", err)
|
||||
}
|
||||
tp.logger.Debug("created self-signed JWT", "token", tok)
|
||||
return &auth.Token{Value: tok, Type: internal.TokenTypeBearer, Expiry: exp}, nil
|
||||
}
|
||||
247
src/server/vendor/cloud.google.com/go/auth/httptransport/httptransport.go
generated
vendored
Normal file
247
src/server/vendor/cloud.google.com/go/auth/httptransport/httptransport.go
generated
vendored
Normal file
@ -0,0 +1,247 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package httptransport provides functionality for managing HTTP client
|
||||
// connections to Google Cloud services.
|
||||
package httptransport
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"cloud.google.com/go/auth"
|
||||
detect "cloud.google.com/go/auth/credentials"
|
||||
"cloud.google.com/go/auth/internal"
|
||||
"cloud.google.com/go/auth/internal/transport"
|
||||
"github.com/googleapis/gax-go/v2/internallog"
|
||||
)
|
||||
|
||||
// ClientCertProvider is a function that returns a TLS client certificate to be
|
||||
// used when opening TLS connections. It follows the same semantics as
|
||||
// [crypto/tls.Config.GetClientCertificate].
|
||||
type ClientCertProvider = func(*tls.CertificateRequestInfo) (*tls.Certificate, error)
|
||||
|
||||
// Options used to configure a [net/http.Client] from [NewClient].
|
||||
type Options struct {
|
||||
// DisableTelemetry disables default telemetry (OpenTelemetry). An example
|
||||
// reason to do so would be to bind custom telemetry that overrides the
|
||||
// defaults.
|
||||
DisableTelemetry bool
|
||||
// DisableAuthentication specifies that no authentication should be used. It
|
||||
// is suitable only for testing and for accessing public resources, like
|
||||
// public Google Cloud Storage buckets.
|
||||
DisableAuthentication bool
|
||||
// Headers are extra HTTP headers that will be appended to every outgoing
|
||||
// request.
|
||||
Headers http.Header
|
||||
// BaseRoundTripper overrides the base transport used for serving requests.
|
||||
// If specified ClientCertProvider is ignored.
|
||||
BaseRoundTripper http.RoundTripper
|
||||
// Endpoint overrides the default endpoint to be used for a service.
|
||||
Endpoint string
|
||||
// APIKey specifies an API key to be used as the basis for authentication.
|
||||
// If set DetectOpts are ignored.
|
||||
APIKey string
|
||||
// Credentials used to add Authorization header to all requests. If set
|
||||
// DetectOpts are ignored.
|
||||
Credentials *auth.Credentials
|
||||
// ClientCertProvider is a function that returns a TLS client certificate to
|
||||
// be used when opening TLS connections. It follows the same semantics as
|
||||
// crypto/tls.Config.GetClientCertificate.
|
||||
ClientCertProvider ClientCertProvider
|
||||
// DetectOpts configures settings for detect Application Default
|
||||
// Credentials.
|
||||
DetectOpts *detect.DetectOptions
|
||||
// UniverseDomain is the default service domain for a given Cloud universe.
|
||||
// The default value is "googleapis.com". This is the universe domain
|
||||
// configured for the client, which will be compared to the universe domain
|
||||
// that is separately configured for the credentials.
|
||||
UniverseDomain string
|
||||
// Logger is used for debug logging. If provided, logging will be enabled
|
||||
// at the loggers configured level. By default logging is disabled unless
|
||||
// enabled by setting GOOGLE_SDK_GO_LOGGING_LEVEL in which case a default
|
||||
// logger will be used. Optional.
|
||||
Logger *slog.Logger
|
||||
|
||||
// InternalOptions are NOT meant to be set directly by consumers of this
|
||||
// package, they should only be set by generated client code.
|
||||
InternalOptions *InternalOptions
|
||||
}
|
||||
|
||||
func (o *Options) validate() error {
|
||||
if o == nil {
|
||||
return errors.New("httptransport: opts required to be non-nil")
|
||||
}
|
||||
if o.InternalOptions != nil && o.InternalOptions.SkipValidation {
|
||||
return nil
|
||||
}
|
||||
hasCreds := o.APIKey != "" ||
|
||||
o.Credentials != nil ||
|
||||
(o.DetectOpts != nil && len(o.DetectOpts.CredentialsJSON) > 0) ||
|
||||
(o.DetectOpts != nil && o.DetectOpts.CredentialsFile != "")
|
||||
if o.DisableAuthentication && hasCreds {
|
||||
return errors.New("httptransport: DisableAuthentication is incompatible with options that set or detect credentials")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// client returns the client a user set for the detect options or nil if one was
|
||||
// not set.
|
||||
func (o *Options) client() *http.Client {
|
||||
if o.DetectOpts != nil && o.DetectOpts.Client != nil {
|
||||
return o.DetectOpts.Client
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Options) logger() *slog.Logger {
|
||||
return internallog.New(o.Logger)
|
||||
}
|
||||
|
||||
func (o *Options) resolveDetectOptions() *detect.DetectOptions {
|
||||
io := o.InternalOptions
|
||||
// soft-clone these so we are not updating a ref the user holds and may reuse
|
||||
do := transport.CloneDetectOptions(o.DetectOpts)
|
||||
|
||||
// If scoped JWTs are enabled user provided an aud, allow self-signed JWT.
|
||||
if (io != nil && io.EnableJWTWithScope) || do.Audience != "" {
|
||||
do.UseSelfSignedJWT = true
|
||||
}
|
||||
// Only default scopes if user did not also set an audience.
|
||||
if len(do.Scopes) == 0 && do.Audience == "" && io != nil && len(io.DefaultScopes) > 0 {
|
||||
do.Scopes = make([]string, len(io.DefaultScopes))
|
||||
copy(do.Scopes, io.DefaultScopes)
|
||||
}
|
||||
if len(do.Scopes) == 0 && do.Audience == "" && io != nil {
|
||||
do.Audience = o.InternalOptions.DefaultAudience
|
||||
}
|
||||
if o.ClientCertProvider != nil {
|
||||
tlsConfig := &tls.Config{
|
||||
GetClientCertificate: o.ClientCertProvider,
|
||||
}
|
||||
do.Client = transport.DefaultHTTPClientWithTLS(tlsConfig)
|
||||
do.TokenURL = detect.GoogleMTLSTokenURL
|
||||
}
|
||||
if do.Logger == nil {
|
||||
do.Logger = o.logger()
|
||||
}
|
||||
return do
|
||||
}
|
||||
|
||||
// InternalOptions are only meant to be set by generated client code. These are
|
||||
// not meant to be set directly by consumers of this package. Configuration in
|
||||
// this type is considered EXPERIMENTAL and may be removed at any time in the
|
||||
// future without warning.
|
||||
type InternalOptions struct {
|
||||
// EnableJWTWithScope specifies if scope can be used with self-signed JWT.
|
||||
EnableJWTWithScope bool
|
||||
// DefaultAudience specifies a default audience to be used as the audience
|
||||
// field ("aud") for the JWT token authentication.
|
||||
DefaultAudience string
|
||||
// DefaultEndpointTemplate combined with UniverseDomain specifies the
|
||||
// default endpoint.
|
||||
DefaultEndpointTemplate string
|
||||
// DefaultMTLSEndpoint specifies the default mTLS endpoint.
|
||||
DefaultMTLSEndpoint string
|
||||
// DefaultScopes specifies the default OAuth2 scopes to be used for a
|
||||
// service.
|
||||
DefaultScopes []string
|
||||
// SkipValidation bypasses validation on Options. It should only be used
|
||||
// internally for clients that need more control over their transport.
|
||||
SkipValidation bool
|
||||
// SkipUniverseDomainValidation skips the verification that the universe
|
||||
// domain configured for the client matches the universe domain configured
|
||||
// for the credentials. It should only be used internally for clients that
|
||||
// need more control over their transport. The default is false.
|
||||
SkipUniverseDomainValidation bool
|
||||
}
|
||||
|
||||
// AddAuthorizationMiddleware adds a middleware to the provided client's
|
||||
// transport that sets the Authorization header with the value produced by the
|
||||
// provided [cloud.google.com/go/auth.Credentials]. An error is returned only
|
||||
// if client or creds is nil.
|
||||
//
|
||||
// This function does not support setting a universe domain value on the client.
|
||||
func AddAuthorizationMiddleware(client *http.Client, creds *auth.Credentials) error {
|
||||
if client == nil || creds == nil {
|
||||
return fmt.Errorf("httptransport: client and tp must not be nil")
|
||||
}
|
||||
base := client.Transport
|
||||
if base == nil {
|
||||
if dt, ok := http.DefaultTransport.(*http.Transport); ok {
|
||||
base = dt.Clone()
|
||||
} else {
|
||||
// Directly reuse the DefaultTransport if the application has
|
||||
// replaced it with an implementation of RoundTripper other than
|
||||
// http.Transport.
|
||||
base = http.DefaultTransport
|
||||
}
|
||||
}
|
||||
client.Transport = &authTransport{
|
||||
creds: creds,
|
||||
base: base,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewClient returns a [net/http.Client] that can be used to communicate with a
|
||||
// Google cloud service, configured with the provided [Options]. It
|
||||
// automatically appends Authorization headers to all outgoing requests.
|
||||
func NewClient(opts *Options) (*http.Client, error) {
|
||||
if err := opts.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tOpts := &transport.Options{
|
||||
Endpoint: opts.Endpoint,
|
||||
ClientCertProvider: opts.ClientCertProvider,
|
||||
Client: opts.client(),
|
||||
UniverseDomain: opts.UniverseDomain,
|
||||
Logger: opts.logger(),
|
||||
}
|
||||
if io := opts.InternalOptions; io != nil {
|
||||
tOpts.DefaultEndpointTemplate = io.DefaultEndpointTemplate
|
||||
tOpts.DefaultMTLSEndpoint = io.DefaultMTLSEndpoint
|
||||
}
|
||||
clientCertProvider, dialTLSContext, err := transport.GetHTTPTransportConfig(tOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
baseRoundTripper := opts.BaseRoundTripper
|
||||
if baseRoundTripper == nil {
|
||||
baseRoundTripper = defaultBaseTransport(clientCertProvider, dialTLSContext)
|
||||
}
|
||||
// Ensure the token exchange transport uses the same ClientCertProvider as the API transport.
|
||||
opts.ClientCertProvider = clientCertProvider
|
||||
trans, err := newTransport(baseRoundTripper, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &http.Client{
|
||||
Transport: trans,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SetAuthHeader uses the provided token to set the Authorization header on a
|
||||
// request. If the token.Type is empty, the type is assumed to be Bearer.
|
||||
func SetAuthHeader(token *auth.Token, req *http.Request) {
|
||||
typ := token.Type
|
||||
if typ == "" {
|
||||
typ = internal.TokenTypeBearer
|
||||
}
|
||||
req.Header.Set("Authorization", typ+" "+token.Value)
|
||||
}
|
||||
234
src/server/vendor/cloud.google.com/go/auth/httptransport/transport.go
generated
vendored
Normal file
234
src/server/vendor/cloud.google.com/go/auth/httptransport/transport.go
generated
vendored
Normal file
@ -0,0 +1,234 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package httptransport
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/auth"
|
||||
"cloud.google.com/go/auth/credentials"
|
||||
"cloud.google.com/go/auth/internal"
|
||||
"cloud.google.com/go/auth/internal/transport"
|
||||
"cloud.google.com/go/auth/internal/transport/cert"
|
||||
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
const (
|
||||
quotaProjectHeaderKey = "X-goog-user-project"
|
||||
)
|
||||
|
||||
func newTransport(base http.RoundTripper, opts *Options) (http.RoundTripper, error) {
|
||||
var headers = opts.Headers
|
||||
ht := &headerTransport{
|
||||
base: base,
|
||||
headers: headers,
|
||||
}
|
||||
var trans http.RoundTripper = ht
|
||||
trans = addOpenTelemetryTransport(trans, opts)
|
||||
switch {
|
||||
case opts.DisableAuthentication:
|
||||
// Do nothing.
|
||||
case opts.APIKey != "":
|
||||
qp := internal.GetQuotaProject(nil, opts.Headers.Get(quotaProjectHeaderKey))
|
||||
if qp != "" {
|
||||
if headers == nil {
|
||||
headers = make(map[string][]string, 1)
|
||||
}
|
||||
headers.Set(quotaProjectHeaderKey, qp)
|
||||
}
|
||||
trans = &apiKeyTransport{
|
||||
Transport: trans,
|
||||
Key: opts.APIKey,
|
||||
}
|
||||
default:
|
||||
var creds *auth.Credentials
|
||||
if opts.Credentials != nil {
|
||||
creds = opts.Credentials
|
||||
} else {
|
||||
var err error
|
||||
creds, err = credentials.DetectDefault(opts.resolveDetectOptions())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
qp, err := creds.QuotaProjectID(context.Background())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if qp != "" {
|
||||
if headers == nil {
|
||||
headers = make(map[string][]string, 1)
|
||||
}
|
||||
// Don't overwrite user specified quota
|
||||
if v := headers.Get(quotaProjectHeaderKey); v == "" {
|
||||
headers.Set(quotaProjectHeaderKey, qp)
|
||||
}
|
||||
}
|
||||
var skipUD bool
|
||||
if iOpts := opts.InternalOptions; iOpts != nil {
|
||||
skipUD = iOpts.SkipUniverseDomainValidation
|
||||
}
|
||||
creds.TokenProvider = auth.NewCachedTokenProvider(creds.TokenProvider, nil)
|
||||
trans = &authTransport{
|
||||
base: trans,
|
||||
creds: creds,
|
||||
clientUniverseDomain: opts.UniverseDomain,
|
||||
skipUniverseDomainValidation: skipUD,
|
||||
}
|
||||
}
|
||||
return trans, nil
|
||||
}
|
||||
|
||||
// defaultBaseTransport returns the base HTTP transport.
|
||||
// On App Engine, this is urlfetch.Transport.
|
||||
// Otherwise, use a default transport, taking most defaults from
|
||||
// http.DefaultTransport.
|
||||
// If TLSCertificate is available, set TLSClientConfig as well.
|
||||
func defaultBaseTransport(clientCertSource cert.Provider, dialTLSContext func(context.Context, string, string) (net.Conn, error)) http.RoundTripper {
|
||||
defaultTransport, ok := http.DefaultTransport.(*http.Transport)
|
||||
if !ok {
|
||||
defaultTransport = transport.BaseTransport()
|
||||
}
|
||||
trans := defaultTransport.Clone()
|
||||
trans.MaxIdleConnsPerHost = 100
|
||||
|
||||
if clientCertSource != nil {
|
||||
trans.TLSClientConfig = &tls.Config{
|
||||
GetClientCertificate: clientCertSource,
|
||||
}
|
||||
}
|
||||
if dialTLSContext != nil {
|
||||
// If DialTLSContext is set, TLSClientConfig wil be ignored
|
||||
trans.DialTLSContext = dialTLSContext
|
||||
}
|
||||
|
||||
// Configures the ReadIdleTimeout HTTP/2 option for the
|
||||
// transport. This allows broken idle connections to be pruned more quickly,
|
||||
// preventing the client from attempting to re-use connections that will no
|
||||
// longer work.
|
||||
http2Trans, err := http2.ConfigureTransports(trans)
|
||||
if err == nil {
|
||||
http2Trans.ReadIdleTimeout = time.Second * 31
|
||||
}
|
||||
|
||||
return trans
|
||||
}
|
||||
|
||||
type apiKeyTransport struct {
|
||||
// Key is the API Key to set on requests.
|
||||
Key string
|
||||
// Transport is the underlying HTTP transport.
|
||||
// If nil, http.DefaultTransport is used.
|
||||
Transport http.RoundTripper
|
||||
}
|
||||
|
||||
func (t *apiKeyTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
newReq := *req
|
||||
args := newReq.URL.Query()
|
||||
args.Set("key", t.Key)
|
||||
newReq.URL.RawQuery = args.Encode()
|
||||
return t.Transport.RoundTrip(&newReq)
|
||||
}
|
||||
|
||||
type headerTransport struct {
|
||||
headers http.Header
|
||||
base http.RoundTripper
|
||||
}
|
||||
|
||||
func (t *headerTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
rt := t.base
|
||||
newReq := *req
|
||||
newReq.Header = make(http.Header)
|
||||
for k, vv := range req.Header {
|
||||
newReq.Header[k] = vv
|
||||
}
|
||||
|
||||
for k, v := range t.headers {
|
||||
newReq.Header[k] = v
|
||||
}
|
||||
|
||||
return rt.RoundTrip(&newReq)
|
||||
}
|
||||
|
||||
func addOpenTelemetryTransport(trans http.RoundTripper, opts *Options) http.RoundTripper {
|
||||
if opts.DisableTelemetry {
|
||||
return trans
|
||||
}
|
||||
return otelhttp.NewTransport(trans)
|
||||
}
|
||||
|
||||
type authTransport struct {
|
||||
creds *auth.Credentials
|
||||
base http.RoundTripper
|
||||
clientUniverseDomain string
|
||||
skipUniverseDomainValidation bool
|
||||
}
|
||||
|
||||
// getClientUniverseDomain returns the default service domain for a given Cloud
|
||||
// universe, with the following precedence:
|
||||
//
|
||||
// 1. A non-empty option.WithUniverseDomain or similar client option.
|
||||
// 2. A non-empty environment variable GOOGLE_CLOUD_UNIVERSE_DOMAIN.
|
||||
// 3. The default value "googleapis.com".
|
||||
//
|
||||
// This is the universe domain configured for the client, which will be compared
|
||||
// to the universe domain that is separately configured for the credentials.
|
||||
func (t *authTransport) getClientUniverseDomain() string {
|
||||
if t.clientUniverseDomain != "" {
|
||||
return t.clientUniverseDomain
|
||||
}
|
||||
if envUD := os.Getenv(internal.UniverseDomainEnvVar); envUD != "" {
|
||||
return envUD
|
||||
}
|
||||
return internal.DefaultUniverseDomain
|
||||
}
|
||||
|
||||
// RoundTrip authorizes and authenticates the request with an
|
||||
// access token from Transport's Source. Per the RoundTripper contract we must
|
||||
// not modify the initial request, so we clone it, and we must close the body
|
||||
// on any errors that happens during our token logic.
|
||||
func (t *authTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
reqBodyClosed := false
|
||||
if req.Body != nil {
|
||||
defer func() {
|
||||
if !reqBodyClosed {
|
||||
req.Body.Close()
|
||||
}
|
||||
}()
|
||||
}
|
||||
token, err := t.creds.Token(req.Context())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !t.skipUniverseDomainValidation && token.MetadataString("auth.google.tokenSource") != "compute-metadata" {
|
||||
credentialsUniverseDomain, err := t.creds.UniverseDomain(req.Context())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := transport.ValidateUniverseDomain(t.getClientUniverseDomain(), credentialsUniverseDomain); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
req2 := req.Clone(req.Context())
|
||||
SetAuthHeader(token, req2)
|
||||
reqBodyClosed = true
|
||||
return t.base.RoundTrip(req2)
|
||||
}
|
||||
107
src/server/vendor/cloud.google.com/go/auth/internal/credsfile/credsfile.go
generated
vendored
Normal file
107
src/server/vendor/cloud.google.com/go/auth/internal/credsfile/credsfile.go
generated
vendored
Normal file
@ -0,0 +1,107 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package credsfile is meant to hide implementation details from the pubic
|
||||
// surface of the detect package. It should not import any other packages in
|
||||
// this module. It is located under the main internal package so other
|
||||
// sub-packages can use these parsed types as well.
|
||||
package credsfile
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
const (
|
||||
// GoogleAppCredsEnvVar is the environment variable for setting the
|
||||
// application default credentials.
|
||||
GoogleAppCredsEnvVar = "GOOGLE_APPLICATION_CREDENTIALS"
|
||||
userCredsFilename = "application_default_credentials.json"
|
||||
)
|
||||
|
||||
// CredentialType represents different credential filetypes Google credentials
|
||||
// can be.
|
||||
type CredentialType int
|
||||
|
||||
const (
|
||||
// UnknownCredType is an unidentified file type.
|
||||
UnknownCredType CredentialType = iota
|
||||
// UserCredentialsKey represents a user creds file type.
|
||||
UserCredentialsKey
|
||||
// ServiceAccountKey represents a service account file type.
|
||||
ServiceAccountKey
|
||||
// ImpersonatedServiceAccountKey represents a impersonated service account
|
||||
// file type.
|
||||
ImpersonatedServiceAccountKey
|
||||
// ExternalAccountKey represents a external account file type.
|
||||
ExternalAccountKey
|
||||
// GDCHServiceAccountKey represents a GDCH file type.
|
||||
GDCHServiceAccountKey
|
||||
// ExternalAccountAuthorizedUserKey represents a external account authorized
|
||||
// user file type.
|
||||
ExternalAccountAuthorizedUserKey
|
||||
)
|
||||
|
||||
// parseCredentialType returns the associated filetype based on the parsed
|
||||
// typeString provided.
|
||||
func parseCredentialType(typeString string) CredentialType {
|
||||
switch typeString {
|
||||
case "service_account":
|
||||
return ServiceAccountKey
|
||||
case "authorized_user":
|
||||
return UserCredentialsKey
|
||||
case "impersonated_service_account":
|
||||
return ImpersonatedServiceAccountKey
|
||||
case "external_account":
|
||||
return ExternalAccountKey
|
||||
case "external_account_authorized_user":
|
||||
return ExternalAccountAuthorizedUserKey
|
||||
case "gdch_service_account":
|
||||
return GDCHServiceAccountKey
|
||||
default:
|
||||
return UnknownCredType
|
||||
}
|
||||
}
|
||||
|
||||
// GetFileNameFromEnv returns the override if provided or detects a filename
|
||||
// from the environment.
|
||||
func GetFileNameFromEnv(override string) string {
|
||||
if override != "" {
|
||||
return override
|
||||
}
|
||||
return os.Getenv(GoogleAppCredsEnvVar)
|
||||
}
|
||||
|
||||
// GetWellKnownFileName tries to locate the filepath for the user credential
|
||||
// file based on the environment.
|
||||
func GetWellKnownFileName() string {
|
||||
if runtime.GOOS == "windows" {
|
||||
return filepath.Join(os.Getenv("APPDATA"), "gcloud", userCredsFilename)
|
||||
}
|
||||
return filepath.Join(guessUnixHomeDir(), ".config", "gcloud", userCredsFilename)
|
||||
}
|
||||
|
||||
// guessUnixHomeDir default to checking for HOME, but not all unix systems have
|
||||
// this set, do have a fallback.
|
||||
func guessUnixHomeDir() string {
|
||||
if v := os.Getenv("HOME"); v != "" {
|
||||
return v
|
||||
}
|
||||
if u, err := user.Current(); err == nil {
|
||||
return u.HomeDir
|
||||
}
|
||||
return ""
|
||||
}
|
||||
157
src/server/vendor/cloud.google.com/go/auth/internal/credsfile/filetype.go
generated
vendored
Normal file
157
src/server/vendor/cloud.google.com/go/auth/internal/credsfile/filetype.go
generated
vendored
Normal file
@ -0,0 +1,157 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package credsfile
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// Config3LO is the internals of a client creds file.
|
||||
type Config3LO struct {
|
||||
ClientID string `json:"client_id"`
|
||||
ClientSecret string `json:"client_secret"`
|
||||
RedirectURIs []string `json:"redirect_uris"`
|
||||
AuthURI string `json:"auth_uri"`
|
||||
TokenURI string `json:"token_uri"`
|
||||
}
|
||||
|
||||
// ClientCredentialsFile representation.
|
||||
type ClientCredentialsFile struct {
|
||||
Web *Config3LO `json:"web"`
|
||||
Installed *Config3LO `json:"installed"`
|
||||
UniverseDomain string `json:"universe_domain"`
|
||||
}
|
||||
|
||||
// ServiceAccountFile representation.
|
||||
type ServiceAccountFile struct {
|
||||
Type string `json:"type"`
|
||||
ProjectID string `json:"project_id"`
|
||||
PrivateKeyID string `json:"private_key_id"`
|
||||
PrivateKey string `json:"private_key"`
|
||||
ClientEmail string `json:"client_email"`
|
||||
ClientID string `json:"client_id"`
|
||||
AuthURL string `json:"auth_uri"`
|
||||
TokenURL string `json:"token_uri"`
|
||||
UniverseDomain string `json:"universe_domain"`
|
||||
}
|
||||
|
||||
// UserCredentialsFile representation.
|
||||
type UserCredentialsFile struct {
|
||||
Type string `json:"type"`
|
||||
ClientID string `json:"client_id"`
|
||||
ClientSecret string `json:"client_secret"`
|
||||
QuotaProjectID string `json:"quota_project_id"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
UniverseDomain string `json:"universe_domain"`
|
||||
}
|
||||
|
||||
// ExternalAccountFile representation.
|
||||
type ExternalAccountFile struct {
|
||||
Type string `json:"type"`
|
||||
ClientID string `json:"client_id"`
|
||||
ClientSecret string `json:"client_secret"`
|
||||
Audience string `json:"audience"`
|
||||
SubjectTokenType string `json:"subject_token_type"`
|
||||
ServiceAccountImpersonationURL string `json:"service_account_impersonation_url"`
|
||||
TokenURL string `json:"token_url"`
|
||||
CredentialSource *CredentialSource `json:"credential_source,omitempty"`
|
||||
TokenInfoURL string `json:"token_info_url"`
|
||||
ServiceAccountImpersonation *ServiceAccountImpersonationInfo `json:"service_account_impersonation,omitempty"`
|
||||
QuotaProjectID string `json:"quota_project_id"`
|
||||
WorkforcePoolUserProject string `json:"workforce_pool_user_project"`
|
||||
UniverseDomain string `json:"universe_domain"`
|
||||
}
|
||||
|
||||
// ExternalAccountAuthorizedUserFile representation.
|
||||
type ExternalAccountAuthorizedUserFile struct {
|
||||
Type string `json:"type"`
|
||||
Audience string `json:"audience"`
|
||||
ClientID string `json:"client_id"`
|
||||
ClientSecret string `json:"client_secret"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
TokenURL string `json:"token_url"`
|
||||
TokenInfoURL string `json:"token_info_url"`
|
||||
RevokeURL string `json:"revoke_url"`
|
||||
QuotaProjectID string `json:"quota_project_id"`
|
||||
UniverseDomain string `json:"universe_domain"`
|
||||
}
|
||||
|
||||
// CredentialSource stores the information necessary to retrieve the credentials for the STS exchange.
|
||||
//
|
||||
// One field amongst File, URL, Certificate, and Executable should be filled, depending on the kind of credential in question.
|
||||
// The EnvironmentID should start with AWS if being used for an AWS credential.
|
||||
type CredentialSource struct {
|
||||
File string `json:"file"`
|
||||
URL string `json:"url"`
|
||||
Headers map[string]string `json:"headers"`
|
||||
Executable *ExecutableConfig `json:"executable,omitempty"`
|
||||
Certificate *CertificateConfig `json:"certificate"`
|
||||
EnvironmentID string `json:"environment_id"` // TODO: Make type for this
|
||||
RegionURL string `json:"region_url"`
|
||||
RegionalCredVerificationURL string `json:"regional_cred_verification_url"`
|
||||
CredVerificationURL string `json:"cred_verification_url"`
|
||||
IMDSv2SessionTokenURL string `json:"imdsv2_session_token_url"`
|
||||
Format *Format `json:"format,omitempty"`
|
||||
}
|
||||
|
||||
// Format describes the format of a [CredentialSource].
|
||||
type Format struct {
|
||||
// Type is either "text" or "json". When not provided "text" type is assumed.
|
||||
Type string `json:"type"`
|
||||
// SubjectTokenFieldName is only required for JSON format. This would be "access_token" for azure.
|
||||
SubjectTokenFieldName string `json:"subject_token_field_name"`
|
||||
}
|
||||
|
||||
// ExecutableConfig represents the command to run for an executable
|
||||
// [CredentialSource].
|
||||
type ExecutableConfig struct {
|
||||
Command string `json:"command"`
|
||||
TimeoutMillis int `json:"timeout_millis"`
|
||||
OutputFile string `json:"output_file"`
|
||||
}
|
||||
|
||||
// CertificateConfig represents the options used to set up X509 based workload
|
||||
// [CredentialSource]
|
||||
type CertificateConfig struct {
|
||||
UseDefaultCertificateConfig bool `json:"use_default_certificate_config"`
|
||||
CertificateConfigLocation string `json:"certificate_config_location"`
|
||||
}
|
||||
|
||||
// ServiceAccountImpersonationInfo has impersonation configuration.
|
||||
type ServiceAccountImpersonationInfo struct {
|
||||
TokenLifetimeSeconds int `json:"token_lifetime_seconds"`
|
||||
}
|
||||
|
||||
// ImpersonatedServiceAccountFile representation.
|
||||
type ImpersonatedServiceAccountFile struct {
|
||||
Type string `json:"type"`
|
||||
ServiceAccountImpersonationURL string `json:"service_account_impersonation_url"`
|
||||
Delegates []string `json:"delegates"`
|
||||
CredSource json.RawMessage `json:"source_credentials"`
|
||||
UniverseDomain string `json:"universe_domain"`
|
||||
}
|
||||
|
||||
// GDCHServiceAccountFile represents the Google Distributed Cloud Hosted (GDCH) service identity file.
|
||||
type GDCHServiceAccountFile struct {
|
||||
Type string `json:"type"`
|
||||
FormatVersion string `json:"format_version"`
|
||||
Project string `json:"project"`
|
||||
Name string `json:"name"`
|
||||
CertPath string `json:"ca_cert_path"`
|
||||
PrivateKeyID string `json:"private_key_id"`
|
||||
PrivateKey string `json:"private_key"`
|
||||
TokenURL string `json:"token_uri"`
|
||||
UniverseDomain string `json:"universe_domain"`
|
||||
}
|
||||
98
src/server/vendor/cloud.google.com/go/auth/internal/credsfile/parse.go
generated
vendored
Normal file
98
src/server/vendor/cloud.google.com/go/auth/internal/credsfile/parse.go
generated
vendored
Normal file
@ -0,0 +1,98 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package credsfile
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// ParseServiceAccount parses bytes into a [ServiceAccountFile].
|
||||
func ParseServiceAccount(b []byte) (*ServiceAccountFile, error) {
|
||||
var f *ServiceAccountFile
|
||||
if err := json.Unmarshal(b, &f); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// ParseClientCredentials parses bytes into a
|
||||
// [credsfile.ClientCredentialsFile].
|
||||
func ParseClientCredentials(b []byte) (*ClientCredentialsFile, error) {
|
||||
var f *ClientCredentialsFile
|
||||
if err := json.Unmarshal(b, &f); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// ParseUserCredentials parses bytes into a [UserCredentialsFile].
|
||||
func ParseUserCredentials(b []byte) (*UserCredentialsFile, error) {
|
||||
var f *UserCredentialsFile
|
||||
if err := json.Unmarshal(b, &f); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// ParseExternalAccount parses bytes into a [ExternalAccountFile].
|
||||
func ParseExternalAccount(b []byte) (*ExternalAccountFile, error) {
|
||||
var f *ExternalAccountFile
|
||||
if err := json.Unmarshal(b, &f); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// ParseExternalAccountAuthorizedUser parses bytes into a
|
||||
// [ExternalAccountAuthorizedUserFile].
|
||||
func ParseExternalAccountAuthorizedUser(b []byte) (*ExternalAccountAuthorizedUserFile, error) {
|
||||
var f *ExternalAccountAuthorizedUserFile
|
||||
if err := json.Unmarshal(b, &f); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// ParseImpersonatedServiceAccount parses bytes into a
|
||||
// [ImpersonatedServiceAccountFile].
|
||||
func ParseImpersonatedServiceAccount(b []byte) (*ImpersonatedServiceAccountFile, error) {
|
||||
var f *ImpersonatedServiceAccountFile
|
||||
if err := json.Unmarshal(b, &f); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// ParseGDCHServiceAccount parses bytes into a [GDCHServiceAccountFile].
|
||||
func ParseGDCHServiceAccount(b []byte) (*GDCHServiceAccountFile, error) {
|
||||
var f *GDCHServiceAccountFile
|
||||
if err := json.Unmarshal(b, &f); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
type fileTypeChecker struct {
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// ParseFileType determines the [CredentialType] based on bytes provided.
|
||||
func ParseFileType(b []byte) (CredentialType, error) {
|
||||
var f fileTypeChecker
|
||||
if err := json.Unmarshal(b, &f); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return parseCredentialType(f.Type), nil
|
||||
}
|
||||
225
src/server/vendor/cloud.google.com/go/auth/internal/internal.go
generated
vendored
Normal file
225
src/server/vendor/cloud.google.com/go/auth/internal/internal.go
generated
vendored
Normal file
@ -0,0 +1,225 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/compute/metadata"
|
||||
)
|
||||
|
||||
const (
|
||||
// TokenTypeBearer is the auth header prefix for bearer tokens.
|
||||
TokenTypeBearer = "Bearer"
|
||||
|
||||
// QuotaProjectEnvVar is the environment variable for setting the quota
|
||||
// project.
|
||||
QuotaProjectEnvVar = "GOOGLE_CLOUD_QUOTA_PROJECT"
|
||||
// UniverseDomainEnvVar is the environment variable for setting the default
|
||||
// service domain for a given Cloud universe.
|
||||
UniverseDomainEnvVar = "GOOGLE_CLOUD_UNIVERSE_DOMAIN"
|
||||
projectEnvVar = "GOOGLE_CLOUD_PROJECT"
|
||||
maxBodySize = 1 << 20
|
||||
|
||||
// DefaultUniverseDomain is the default value for universe domain.
|
||||
// Universe domain is the default service domain for a given Cloud universe.
|
||||
DefaultUniverseDomain = "googleapis.com"
|
||||
)
|
||||
|
||||
type clonableTransport interface {
|
||||
Clone() *http.Transport
|
||||
}
|
||||
|
||||
// DefaultClient returns an [http.Client] with some defaults set. If
|
||||
// the current [http.DefaultTransport] is a [clonableTransport], as
|
||||
// is the case for an [*http.Transport], the clone will be used.
|
||||
// Otherwise the [http.DefaultTransport] is used directly.
|
||||
func DefaultClient() *http.Client {
|
||||
if transport, ok := http.DefaultTransport.(clonableTransport); ok {
|
||||
return &http.Client{
|
||||
Transport: transport.Clone(),
|
||||
Timeout: 30 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
return &http.Client{
|
||||
Transport: http.DefaultTransport,
|
||||
Timeout: 30 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
// ParseKey converts the binary contents of a private key file
|
||||
// to an crypto.Signer. It detects whether the private key is in a
|
||||
// PEM container or not. If so, it extracts the the private key
|
||||
// from PEM container before conversion. It only supports PEM
|
||||
// containers with no passphrase.
|
||||
func ParseKey(key []byte) (crypto.Signer, error) {
|
||||
block, _ := pem.Decode(key)
|
||||
if block != nil {
|
||||
key = block.Bytes
|
||||
}
|
||||
var parsedKey crypto.PrivateKey
|
||||
var err error
|
||||
parsedKey, err = x509.ParsePKCS8PrivateKey(key)
|
||||
if err != nil {
|
||||
parsedKey, err = x509.ParsePKCS1PrivateKey(key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("private key should be a PEM or plain PKCS1 or PKCS8: %w", err)
|
||||
}
|
||||
}
|
||||
parsed, ok := parsedKey.(crypto.Signer)
|
||||
if !ok {
|
||||
return nil, errors.New("private key is not a signer")
|
||||
}
|
||||
return parsed, nil
|
||||
}
|
||||
|
||||
// GetQuotaProject retrieves quota project with precedence being: override,
|
||||
// environment variable, creds json file.
|
||||
func GetQuotaProject(b []byte, override string) string {
|
||||
if override != "" {
|
||||
return override
|
||||
}
|
||||
if env := os.Getenv(QuotaProjectEnvVar); env != "" {
|
||||
return env
|
||||
}
|
||||
if b == nil {
|
||||
return ""
|
||||
}
|
||||
var v struct {
|
||||
QuotaProject string `json:"quota_project_id"`
|
||||
}
|
||||
if err := json.Unmarshal(b, &v); err != nil {
|
||||
return ""
|
||||
}
|
||||
return v.QuotaProject
|
||||
}
|
||||
|
||||
// GetProjectID retrieves project with precedence being: override,
|
||||
// environment variable, creds json file.
|
||||
func GetProjectID(b []byte, override string) string {
|
||||
if override != "" {
|
||||
return override
|
||||
}
|
||||
if env := os.Getenv(projectEnvVar); env != "" {
|
||||
return env
|
||||
}
|
||||
if b == nil {
|
||||
return ""
|
||||
}
|
||||
var v struct {
|
||||
ProjectID string `json:"project_id"` // standard service account key
|
||||
Project string `json:"project"` // gdch key
|
||||
}
|
||||
if err := json.Unmarshal(b, &v); err != nil {
|
||||
return ""
|
||||
}
|
||||
if v.ProjectID != "" {
|
||||
return v.ProjectID
|
||||
}
|
||||
return v.Project
|
||||
}
|
||||
|
||||
// DoRequest executes the provided req with the client. It reads the response
|
||||
// body, closes it, and returns it.
|
||||
func DoRequest(client *http.Client, req *http.Request) (*http.Response, []byte, error) {
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ReadAll(io.LimitReader(resp.Body, maxBodySize))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return resp, body, nil
|
||||
}
|
||||
|
||||
// ReadAll consumes the whole reader and safely reads the content of its body
|
||||
// with some overflow protection.
|
||||
func ReadAll(r io.Reader) ([]byte, error) {
|
||||
return io.ReadAll(io.LimitReader(r, maxBodySize))
|
||||
}
|
||||
|
||||
// StaticCredentialsProperty is a helper for creating static credentials
|
||||
// properties.
|
||||
func StaticCredentialsProperty(s string) StaticProperty {
|
||||
return StaticProperty(s)
|
||||
}
|
||||
|
||||
// StaticProperty always returns that value of the underlying string.
|
||||
type StaticProperty string
|
||||
|
||||
// GetProperty loads the properly value provided the given context.
|
||||
func (p StaticProperty) GetProperty(context.Context) (string, error) {
|
||||
return string(p), nil
|
||||
}
|
||||
|
||||
// ComputeUniverseDomainProvider fetches the credentials universe domain from
|
||||
// the google cloud metadata service.
|
||||
type ComputeUniverseDomainProvider struct {
|
||||
MetadataClient *metadata.Client
|
||||
universeDomainOnce sync.Once
|
||||
universeDomain string
|
||||
universeDomainErr error
|
||||
}
|
||||
|
||||
// GetProperty fetches the credentials universe domain from the google cloud
|
||||
// metadata service.
|
||||
func (c *ComputeUniverseDomainProvider) GetProperty(ctx context.Context) (string, error) {
|
||||
c.universeDomainOnce.Do(func() {
|
||||
c.universeDomain, c.universeDomainErr = getMetadataUniverseDomain(ctx, c.MetadataClient)
|
||||
})
|
||||
if c.universeDomainErr != nil {
|
||||
return "", c.universeDomainErr
|
||||
}
|
||||
return c.universeDomain, nil
|
||||
}
|
||||
|
||||
// httpGetMetadataUniverseDomain is a package var for unit test substitution.
|
||||
var httpGetMetadataUniverseDomain = func(ctx context.Context, client *metadata.Client) (string, error) {
|
||||
ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
|
||||
defer cancel()
|
||||
return client.GetWithContext(ctx, "universe/universe-domain")
|
||||
}
|
||||
|
||||
func getMetadataUniverseDomain(ctx context.Context, client *metadata.Client) (string, error) {
|
||||
universeDomain, err := httpGetMetadataUniverseDomain(ctx, client)
|
||||
if err == nil {
|
||||
return universeDomain, nil
|
||||
}
|
||||
if _, ok := err.(metadata.NotDefinedError); ok {
|
||||
// http.StatusNotFound (404)
|
||||
return DefaultUniverseDomain, nil
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
// FormatIAMServiceAccountResource sets a service account name in an IAM resource
|
||||
// name.
|
||||
func FormatIAMServiceAccountResource(name string) string {
|
||||
return fmt.Sprintf("projects/-/serviceAccounts/%s", name)
|
||||
}
|
||||
171
src/server/vendor/cloud.google.com/go/auth/internal/jwt/jwt.go
generated
vendored
Normal file
171
src/server/vendor/cloud.google.com/go/auth/internal/jwt/jwt.go
generated
vendored
Normal file
@ -0,0 +1,171 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// HeaderAlgRSA256 is the RS256 [Header.Algorithm].
|
||||
HeaderAlgRSA256 = "RS256"
|
||||
// HeaderAlgES256 is the ES256 [Header.Algorithm].
|
||||
HeaderAlgES256 = "ES256"
|
||||
// HeaderType is the standard [Header.Type].
|
||||
HeaderType = "JWT"
|
||||
)
|
||||
|
||||
// Header represents a JWT header.
|
||||
type Header struct {
|
||||
Algorithm string `json:"alg"`
|
||||
Type string `json:"typ"`
|
||||
KeyID string `json:"kid"`
|
||||
}
|
||||
|
||||
func (h *Header) encode() (string, error) {
|
||||
b, err := json.Marshal(h)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64.RawURLEncoding.EncodeToString(b), nil
|
||||
}
|
||||
|
||||
// Claims represents the claims set of a JWT.
|
||||
type Claims struct {
|
||||
// Iss is the issuer JWT claim.
|
||||
Iss string `json:"iss"`
|
||||
// Scope is the scope JWT claim.
|
||||
Scope string `json:"scope,omitempty"`
|
||||
// Exp is the expiry JWT claim. If unset, default is in one hour from now.
|
||||
Exp int64 `json:"exp"`
|
||||
// Iat is the subject issued at claim. If unset, default is now.
|
||||
Iat int64 `json:"iat"`
|
||||
// Aud is the audience JWT claim. Optional.
|
||||
Aud string `json:"aud"`
|
||||
// Sub is the subject JWT claim. Optional.
|
||||
Sub string `json:"sub,omitempty"`
|
||||
// AdditionalClaims contains any additional non-standard JWT claims. Optional.
|
||||
AdditionalClaims map[string]interface{} `json:"-"`
|
||||
}
|
||||
|
||||
func (c *Claims) encode() (string, error) {
|
||||
// Compensate for skew
|
||||
now := time.Now().Add(-10 * time.Second)
|
||||
if c.Iat == 0 {
|
||||
c.Iat = now.Unix()
|
||||
}
|
||||
if c.Exp == 0 {
|
||||
c.Exp = now.Add(time.Hour).Unix()
|
||||
}
|
||||
if c.Exp < c.Iat {
|
||||
return "", fmt.Errorf("jwt: invalid Exp = %d; must be later than Iat = %d", c.Exp, c.Iat)
|
||||
}
|
||||
|
||||
b, err := json.Marshal(c)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(c.AdditionalClaims) == 0 {
|
||||
return base64.RawURLEncoding.EncodeToString(b), nil
|
||||
}
|
||||
|
||||
// Marshal private claim set and then append it to b.
|
||||
prv, err := json.Marshal(c.AdditionalClaims)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid map of additional claims %v: %w", c.AdditionalClaims, err)
|
||||
}
|
||||
|
||||
// Concatenate public and private claim JSON objects.
|
||||
if !bytes.HasSuffix(b, []byte{'}'}) {
|
||||
return "", fmt.Errorf("invalid JSON %s", b)
|
||||
}
|
||||
if !bytes.HasPrefix(prv, []byte{'{'}) {
|
||||
return "", fmt.Errorf("invalid JSON %s", prv)
|
||||
}
|
||||
b[len(b)-1] = ',' // Replace closing curly brace with a comma.
|
||||
b = append(b, prv[1:]...) // Append private claims.
|
||||
return base64.RawURLEncoding.EncodeToString(b), nil
|
||||
}
|
||||
|
||||
// EncodeJWS encodes the data using the provided key as a JSON web signature.
|
||||
func EncodeJWS(header *Header, c *Claims, signer crypto.Signer) (string, error) {
|
||||
head, err := header.encode()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
claims, err := c.encode()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ss := fmt.Sprintf("%s.%s", head, claims)
|
||||
h := sha256.New()
|
||||
h.Write([]byte(ss))
|
||||
sig, err := signer.Sign(rand.Reader, h.Sum(nil), crypto.SHA256)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("%s.%s", ss, base64.RawURLEncoding.EncodeToString(sig)), nil
|
||||
}
|
||||
|
||||
// DecodeJWS decodes a claim set from a JWS payload.
|
||||
func DecodeJWS(payload string) (*Claims, error) {
|
||||
// decode returned id token to get expiry
|
||||
s := strings.Split(payload, ".")
|
||||
if len(s) < 2 {
|
||||
return nil, errors.New("invalid token received")
|
||||
}
|
||||
decoded, err := base64.RawURLEncoding.DecodeString(s[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c := &Claims{}
|
||||
if err := json.NewDecoder(bytes.NewBuffer(decoded)).Decode(c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := json.NewDecoder(bytes.NewBuffer(decoded)).Decode(&c.AdditionalClaims); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c, err
|
||||
}
|
||||
|
||||
// VerifyJWS tests whether the provided JWT token's signature was produced by
|
||||
// the private key associated with the provided public key.
|
||||
func VerifyJWS(token string, key *rsa.PublicKey) error {
|
||||
parts := strings.Split(token, ".")
|
||||
if len(parts) != 3 {
|
||||
return errors.New("jwt: invalid token received, token must have 3 parts")
|
||||
}
|
||||
|
||||
signedContent := parts[0] + "." + parts[1]
|
||||
signatureString, err := base64.RawURLEncoding.DecodeString(parts[2])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h := sha256.New()
|
||||
h.Write([]byte(signedContent))
|
||||
return rsa.VerifyPKCS1v15(key, crypto.SHA256, h.Sum(nil), signatureString)
|
||||
}
|
||||
368
src/server/vendor/cloud.google.com/go/auth/internal/transport/cba.go
generated
vendored
Normal file
368
src/server/vendor/cloud.google.com/go/auth/internal/transport/cba.go
generated
vendored
Normal file
@ -0,0 +1,368 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package transport
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"log"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"cloud.google.com/go/auth/internal"
|
||||
"cloud.google.com/go/auth/internal/transport/cert"
|
||||
"github.com/google/s2a-go"
|
||||
"github.com/google/s2a-go/fallback"
|
||||
"google.golang.org/grpc/credentials"
|
||||
)
|
||||
|
||||
const (
|
||||
mTLSModeAlways = "always"
|
||||
mTLSModeNever = "never"
|
||||
mTLSModeAuto = "auto"
|
||||
|
||||
// Experimental: if true, the code will try MTLS with S2A as the default for transport security. Default value is false.
|
||||
googleAPIUseS2AEnv = "EXPERIMENTAL_GOOGLE_API_USE_S2A"
|
||||
googleAPIUseCertSource = "GOOGLE_API_USE_CLIENT_CERTIFICATE"
|
||||
googleAPIUseMTLS = "GOOGLE_API_USE_MTLS_ENDPOINT"
|
||||
googleAPIUseMTLSOld = "GOOGLE_API_USE_MTLS"
|
||||
|
||||
universeDomainPlaceholder = "UNIVERSE_DOMAIN"
|
||||
|
||||
mtlsMDSRoot = "/run/google-mds-mtls/root.crt"
|
||||
mtlsMDSKey = "/run/google-mds-mtls/client.key"
|
||||
)
|
||||
|
||||
// Options is a struct that is duplicated information from the individual
|
||||
// transport packages in order to avoid cyclic deps. It correlates 1:1 with
|
||||
// fields on httptransport.Options and grpctransport.Options.
|
||||
type Options struct {
|
||||
Endpoint string
|
||||
DefaultEndpointTemplate string
|
||||
DefaultMTLSEndpoint string
|
||||
ClientCertProvider cert.Provider
|
||||
Client *http.Client
|
||||
UniverseDomain string
|
||||
EnableDirectPath bool
|
||||
EnableDirectPathXds bool
|
||||
Logger *slog.Logger
|
||||
}
|
||||
|
||||
// getUniverseDomain returns the default service domain for a given Cloud
|
||||
// universe.
|
||||
func (o *Options) getUniverseDomain() string {
|
||||
if o.UniverseDomain == "" {
|
||||
return internal.DefaultUniverseDomain
|
||||
}
|
||||
return o.UniverseDomain
|
||||
}
|
||||
|
||||
// isUniverseDomainGDU returns true if the universe domain is the default Google
|
||||
// universe.
|
||||
func (o *Options) isUniverseDomainGDU() bool {
|
||||
return o.getUniverseDomain() == internal.DefaultUniverseDomain
|
||||
}
|
||||
|
||||
// defaultEndpoint returns the DefaultEndpointTemplate merged with the
|
||||
// universe domain if the DefaultEndpointTemplate is set, otherwise returns an
|
||||
// empty string.
|
||||
func (o *Options) defaultEndpoint() string {
|
||||
if o.DefaultEndpointTemplate == "" {
|
||||
return ""
|
||||
}
|
||||
return strings.Replace(o.DefaultEndpointTemplate, universeDomainPlaceholder, o.getUniverseDomain(), 1)
|
||||
}
|
||||
|
||||
// defaultMTLSEndpoint returns the DefaultMTLSEndpointTemplate merged with the
|
||||
// universe domain if the DefaultMTLSEndpointTemplate is set, otherwise returns an
|
||||
// empty string.
|
||||
func (o *Options) defaultMTLSEndpoint() string {
|
||||
if o.DefaultMTLSEndpoint == "" {
|
||||
return ""
|
||||
}
|
||||
return strings.Replace(o.DefaultMTLSEndpoint, universeDomainPlaceholder, o.getUniverseDomain(), 1)
|
||||
}
|
||||
|
||||
// mergedEndpoint merges a user-provided Endpoint of format host[:port] with the
|
||||
// default endpoint.
|
||||
func (o *Options) mergedEndpoint() (string, error) {
|
||||
defaultEndpoint := o.defaultEndpoint()
|
||||
u, err := url.Parse(fixScheme(defaultEndpoint))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.Replace(defaultEndpoint, u.Host, o.Endpoint, 1), nil
|
||||
}
|
||||
|
||||
func fixScheme(baseURL string) string {
|
||||
if !strings.Contains(baseURL, "://") {
|
||||
baseURL = "https://" + baseURL
|
||||
}
|
||||
return baseURL
|
||||
}
|
||||
|
||||
// GetGRPCTransportCredsAndEndpoint returns an instance of
|
||||
// [google.golang.org/grpc/credentials.TransportCredentials], and the
|
||||
// corresponding endpoint to use for GRPC client.
|
||||
func GetGRPCTransportCredsAndEndpoint(opts *Options) (credentials.TransportCredentials, string, error) {
|
||||
config, err := getTransportConfig(opts)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
defaultTransportCreds := credentials.NewTLS(&tls.Config{
|
||||
GetClientCertificate: config.clientCertSource,
|
||||
})
|
||||
|
||||
var s2aAddr string
|
||||
var transportCredsForS2A credentials.TransportCredentials
|
||||
|
||||
if config.mtlsS2AAddress != "" {
|
||||
s2aAddr = config.mtlsS2AAddress
|
||||
transportCredsForS2A, err = loadMTLSMDSTransportCreds(mtlsMDSRoot, mtlsMDSKey)
|
||||
if err != nil {
|
||||
log.Printf("Loading MTLS MDS credentials failed: %v", err)
|
||||
if config.s2aAddress != "" {
|
||||
s2aAddr = config.s2aAddress
|
||||
} else {
|
||||
return defaultTransportCreds, config.endpoint, nil
|
||||
}
|
||||
}
|
||||
} else if config.s2aAddress != "" {
|
||||
s2aAddr = config.s2aAddress
|
||||
} else {
|
||||
return defaultTransportCreds, config.endpoint, nil
|
||||
}
|
||||
|
||||
var fallbackOpts *s2a.FallbackOptions
|
||||
// In case of S2A failure, fall back to the endpoint that would've been used without S2A.
|
||||
if fallbackHandshake, err := fallback.DefaultFallbackClientHandshakeFunc(config.endpoint); err == nil {
|
||||
fallbackOpts = &s2a.FallbackOptions{
|
||||
FallbackClientHandshakeFunc: fallbackHandshake,
|
||||
}
|
||||
}
|
||||
|
||||
s2aTransportCreds, err := s2a.NewClientCreds(&s2a.ClientOptions{
|
||||
S2AAddress: s2aAddr,
|
||||
TransportCreds: transportCredsForS2A,
|
||||
FallbackOpts: fallbackOpts,
|
||||
})
|
||||
if err != nil {
|
||||
// Use default if we cannot initialize S2A client transport credentials.
|
||||
return defaultTransportCreds, config.endpoint, nil
|
||||
}
|
||||
return s2aTransportCreds, config.s2aMTLSEndpoint, nil
|
||||
}
|
||||
|
||||
// GetHTTPTransportConfig returns a client certificate source and a function for
|
||||
// dialing MTLS with S2A.
|
||||
func GetHTTPTransportConfig(opts *Options) (cert.Provider, func(context.Context, string, string) (net.Conn, error), error) {
|
||||
config, err := getTransportConfig(opts)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var s2aAddr string
|
||||
var transportCredsForS2A credentials.TransportCredentials
|
||||
|
||||
if config.mtlsS2AAddress != "" {
|
||||
s2aAddr = config.mtlsS2AAddress
|
||||
transportCredsForS2A, err = loadMTLSMDSTransportCreds(mtlsMDSRoot, mtlsMDSKey)
|
||||
if err != nil {
|
||||
log.Printf("Loading MTLS MDS credentials failed: %v", err)
|
||||
if config.s2aAddress != "" {
|
||||
s2aAddr = config.s2aAddress
|
||||
} else {
|
||||
return config.clientCertSource, nil, nil
|
||||
}
|
||||
}
|
||||
} else if config.s2aAddress != "" {
|
||||
s2aAddr = config.s2aAddress
|
||||
} else {
|
||||
return config.clientCertSource, nil, nil
|
||||
}
|
||||
|
||||
var fallbackOpts *s2a.FallbackOptions
|
||||
// In case of S2A failure, fall back to the endpoint that would've been used without S2A.
|
||||
if fallbackURL, err := url.Parse(config.endpoint); err == nil {
|
||||
if fallbackDialer, fallbackServerAddr, err := fallback.DefaultFallbackDialerAndAddress(fallbackURL.Hostname()); err == nil {
|
||||
fallbackOpts = &s2a.FallbackOptions{
|
||||
FallbackDialer: &s2a.FallbackDialer{
|
||||
Dialer: fallbackDialer,
|
||||
ServerAddr: fallbackServerAddr,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dialTLSContextFunc := s2a.NewS2ADialTLSContextFunc(&s2a.ClientOptions{
|
||||
S2AAddress: s2aAddr,
|
||||
TransportCreds: transportCredsForS2A,
|
||||
FallbackOpts: fallbackOpts,
|
||||
})
|
||||
return nil, dialTLSContextFunc, nil
|
||||
}
|
||||
|
||||
func loadMTLSMDSTransportCreds(mtlsMDSRootFile, mtlsMDSKeyFile string) (credentials.TransportCredentials, error) {
|
||||
rootPEM, err := os.ReadFile(mtlsMDSRootFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
caCertPool := x509.NewCertPool()
|
||||
ok := caCertPool.AppendCertsFromPEM(rootPEM)
|
||||
if !ok {
|
||||
return nil, errors.New("failed to load MTLS MDS root certificate")
|
||||
}
|
||||
// The mTLS MDS credentials are formatted as the concatenation of a PEM-encoded certificate chain
|
||||
// followed by a PEM-encoded private key. For this reason, the concatenation is passed in to the
|
||||
// tls.X509KeyPair function as both the certificate chain and private key arguments.
|
||||
cert, err := tls.LoadX509KeyPair(mtlsMDSKeyFile, mtlsMDSKeyFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tlsConfig := tls.Config{
|
||||
RootCAs: caCertPool,
|
||||
Certificates: []tls.Certificate{cert},
|
||||
MinVersion: tls.VersionTLS13,
|
||||
}
|
||||
return credentials.NewTLS(&tlsConfig), nil
|
||||
}
|
||||
|
||||
func getTransportConfig(opts *Options) (*transportConfig, error) {
|
||||
clientCertSource, err := GetClientCertificateProvider(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
endpoint, err := getEndpoint(opts, clientCertSource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defaultTransportConfig := transportConfig{
|
||||
clientCertSource: clientCertSource,
|
||||
endpoint: endpoint,
|
||||
}
|
||||
|
||||
if !shouldUseS2A(clientCertSource, opts) {
|
||||
return &defaultTransportConfig, nil
|
||||
}
|
||||
|
||||
s2aAddress := GetS2AAddress(opts.Logger)
|
||||
mtlsS2AAddress := GetMTLSS2AAddress(opts.Logger)
|
||||
if s2aAddress == "" && mtlsS2AAddress == "" {
|
||||
return &defaultTransportConfig, nil
|
||||
}
|
||||
return &transportConfig{
|
||||
clientCertSource: clientCertSource,
|
||||
endpoint: endpoint,
|
||||
s2aAddress: s2aAddress,
|
||||
mtlsS2AAddress: mtlsS2AAddress,
|
||||
s2aMTLSEndpoint: opts.defaultMTLSEndpoint(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetClientCertificateProvider returns a default client certificate source, if
|
||||
// not provided by the user.
|
||||
//
|
||||
// A nil default source can be returned if the source does not exist. Any exceptions
|
||||
// encountered while initializing the default source will be reported as client
|
||||
// error (ex. corrupt metadata file).
|
||||
func GetClientCertificateProvider(opts *Options) (cert.Provider, error) {
|
||||
if !isClientCertificateEnabled(opts) {
|
||||
return nil, nil
|
||||
} else if opts.ClientCertProvider != nil {
|
||||
return opts.ClientCertProvider, nil
|
||||
}
|
||||
return cert.DefaultProvider()
|
||||
|
||||
}
|
||||
|
||||
// isClientCertificateEnabled returns true by default for all GDU universe domain, unless explicitly overridden by env var
|
||||
func isClientCertificateEnabled(opts *Options) bool {
|
||||
if value, ok := os.LookupEnv(googleAPIUseCertSource); ok {
|
||||
// error as false is OK
|
||||
b, _ := strconv.ParseBool(value)
|
||||
return b
|
||||
}
|
||||
return opts.isUniverseDomainGDU()
|
||||
}
|
||||
|
||||
type transportConfig struct {
|
||||
// The client certificate source.
|
||||
clientCertSource cert.Provider
|
||||
// The corresponding endpoint to use based on client certificate source.
|
||||
endpoint string
|
||||
// The plaintext S2A address if it can be used, otherwise an empty string.
|
||||
s2aAddress string
|
||||
// The MTLS S2A address if it can be used, otherwise an empty string.
|
||||
mtlsS2AAddress string
|
||||
// The MTLS endpoint to use with S2A.
|
||||
s2aMTLSEndpoint string
|
||||
}
|
||||
|
||||
// getEndpoint returns the endpoint for the service, taking into account the
|
||||
// user-provided endpoint override "settings.Endpoint".
|
||||
//
|
||||
// If no endpoint override is specified, we will either return the default
|
||||
// endpoint or the default mTLS endpoint if a client certificate is available.
|
||||
//
|
||||
// You can override the default endpoint choice (mTLS vs. regular) by setting
|
||||
// the GOOGLE_API_USE_MTLS_ENDPOINT environment variable.
|
||||
//
|
||||
// If the endpoint override is an address (host:port) rather than full base
|
||||
// URL (ex. https://...), then the user-provided address will be merged into
|
||||
// the default endpoint. For example, WithEndpoint("myhost:8000") and
|
||||
// DefaultEndpointTemplate("https://UNIVERSE_DOMAIN/bar/baz") will return
|
||||
// "https://myhost:8080/bar/baz". Note that this does not apply to the mTLS
|
||||
// endpoint.
|
||||
func getEndpoint(opts *Options, clientCertSource cert.Provider) (string, error) {
|
||||
if opts.Endpoint == "" {
|
||||
mtlsMode := getMTLSMode()
|
||||
if mtlsMode == mTLSModeAlways || (clientCertSource != nil && mtlsMode == mTLSModeAuto) {
|
||||
return opts.defaultMTLSEndpoint(), nil
|
||||
}
|
||||
return opts.defaultEndpoint(), nil
|
||||
}
|
||||
if strings.Contains(opts.Endpoint, "://") {
|
||||
// User passed in a full URL path, use it verbatim.
|
||||
return opts.Endpoint, nil
|
||||
}
|
||||
if opts.defaultEndpoint() == "" {
|
||||
// If DefaultEndpointTemplate is not configured,
|
||||
// use the user provided endpoint verbatim. This allows a naked
|
||||
// "host[:port]" URL to be used with GRPC Direct Path.
|
||||
return opts.Endpoint, nil
|
||||
}
|
||||
|
||||
// Assume user-provided endpoint is host[:port], merge it with the default endpoint.
|
||||
return opts.mergedEndpoint()
|
||||
}
|
||||
|
||||
func getMTLSMode() string {
|
||||
mode := os.Getenv(googleAPIUseMTLS)
|
||||
if mode == "" {
|
||||
mode = os.Getenv(googleAPIUseMTLSOld) // Deprecated.
|
||||
}
|
||||
if mode == "" {
|
||||
return mTLSModeAuto
|
||||
}
|
||||
return strings.ToLower(mode)
|
||||
}
|
||||
65
src/server/vendor/cloud.google.com/go/auth/internal/transport/cert/default_cert.go
generated
vendored
Normal file
65
src/server/vendor/cloud.google.com/go/auth/internal/transport/cert/default_cert.go
generated
vendored
Normal file
@ -0,0 +1,65 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cert
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// defaultCertData holds all the variables pertaining to
|
||||
// the default certificate provider created by [DefaultProvider].
|
||||
//
|
||||
// A singleton model is used to allow the provider to be reused
|
||||
// by the transport layer. As mentioned in [DefaultProvider] (provider nil, nil)
|
||||
// may be returned to indicate a default provider could not be found, which
|
||||
// will skip extra tls config in the transport layer .
|
||||
type defaultCertData struct {
|
||||
once sync.Once
|
||||
provider Provider
|
||||
err error
|
||||
}
|
||||
|
||||
var (
|
||||
defaultCert defaultCertData
|
||||
)
|
||||
|
||||
// Provider is a function that can be passed into crypto/tls.Config.GetClientCertificate.
|
||||
type Provider func(*tls.CertificateRequestInfo) (*tls.Certificate, error)
|
||||
|
||||
// errSourceUnavailable is a sentinel error to indicate certificate source is unavailable.
|
||||
var errSourceUnavailable = errors.New("certificate source is unavailable")
|
||||
|
||||
// DefaultProvider returns a certificate source using the preferred EnterpriseCertificateProxySource.
|
||||
// If EnterpriseCertificateProxySource is not available, fall back to the legacy SecureConnectSource.
|
||||
//
|
||||
// If neither source is available (due to missing configurations), a nil Source and a nil Error are
|
||||
// returned to indicate that a default certificate source is unavailable.
|
||||
func DefaultProvider() (Provider, error) {
|
||||
defaultCert.once.Do(func() {
|
||||
defaultCert.provider, defaultCert.err = NewWorkloadX509CertProvider("")
|
||||
if errors.Is(defaultCert.err, errSourceUnavailable) {
|
||||
defaultCert.provider, defaultCert.err = NewEnterpriseCertificateProxyProvider("")
|
||||
if errors.Is(defaultCert.err, errSourceUnavailable) {
|
||||
defaultCert.provider, defaultCert.err = NewSecureConnectProvider("")
|
||||
if errors.Is(defaultCert.err, errSourceUnavailable) {
|
||||
defaultCert.provider, defaultCert.err = nil, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
return defaultCert.provider, defaultCert.err
|
||||
}
|
||||
54
src/server/vendor/cloud.google.com/go/auth/internal/transport/cert/enterprise_cert.go
generated
vendored
Normal file
54
src/server/vendor/cloud.google.com/go/auth/internal/transport/cert/enterprise_cert.go
generated
vendored
Normal file
@ -0,0 +1,54 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cert
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
|
||||
"github.com/googleapis/enterprise-certificate-proxy/client"
|
||||
)
|
||||
|
||||
type ecpSource struct {
|
||||
key *client.Key
|
||||
}
|
||||
|
||||
// NewEnterpriseCertificateProxyProvider creates a certificate source
|
||||
// using the Enterprise Certificate Proxy client, which delegates
|
||||
// certifcate related operations to an OS-specific "signer binary"
|
||||
// that communicates with the native keystore (ex. keychain on MacOS).
|
||||
//
|
||||
// The configFilePath points to a config file containing relevant parameters
|
||||
// such as the certificate issuer and the location of the signer binary.
|
||||
// If configFilePath is empty, the client will attempt to load the config from
|
||||
// a well-known gcloud location.
|
||||
func NewEnterpriseCertificateProxyProvider(configFilePath string) (Provider, error) {
|
||||
key, err := client.Cred(configFilePath)
|
||||
if err != nil {
|
||||
// TODO(codyoss): once this is fixed upstream can handle this error a
|
||||
// little better here. But be safe for now and assume unavailable.
|
||||
return nil, errSourceUnavailable
|
||||
}
|
||||
|
||||
return (&ecpSource{
|
||||
key: key,
|
||||
}).getClientCertificate, nil
|
||||
}
|
||||
|
||||
func (s *ecpSource) getClientCertificate(info *tls.CertificateRequestInfo) (*tls.Certificate, error) {
|
||||
var cert tls.Certificate
|
||||
cert.PrivateKey = s.key
|
||||
cert.Certificate = s.key.CertificateChain()
|
||||
return &cert, nil
|
||||
}
|
||||
124
src/server/vendor/cloud.google.com/go/auth/internal/transport/cert/secureconnect_cert.go
generated
vendored
Normal file
124
src/server/vendor/cloud.google.com/go/auth/internal/transport/cert/secureconnect_cert.go
generated
vendored
Normal file
@ -0,0 +1,124 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cert
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
metadataPath = ".secureConnect"
|
||||
metadataFile = "context_aware_metadata.json"
|
||||
)
|
||||
|
||||
type secureConnectSource struct {
|
||||
metadata secureConnectMetadata
|
||||
|
||||
// Cache the cert to avoid executing helper command repeatedly.
|
||||
cachedCertMutex sync.Mutex
|
||||
cachedCert *tls.Certificate
|
||||
}
|
||||
|
||||
type secureConnectMetadata struct {
|
||||
Cmd []string `json:"cert_provider_command"`
|
||||
}
|
||||
|
||||
// NewSecureConnectProvider creates a certificate source using
|
||||
// the Secure Connect Helper and its associated metadata file.
|
||||
//
|
||||
// The configFilePath points to the location of the context aware metadata file.
|
||||
// If configFilePath is empty, use the default context aware metadata location.
|
||||
func NewSecureConnectProvider(configFilePath string) (Provider, error) {
|
||||
if configFilePath == "" {
|
||||
user, err := user.Current()
|
||||
if err != nil {
|
||||
// Error locating the default config means Secure Connect is not supported.
|
||||
return nil, errSourceUnavailable
|
||||
}
|
||||
configFilePath = filepath.Join(user.HomeDir, metadataPath, metadataFile)
|
||||
}
|
||||
|
||||
file, err := os.ReadFile(configFilePath)
|
||||
if err != nil {
|
||||
// Config file missing means Secure Connect is not supported.
|
||||
// There are non-os.ErrNotExist errors that may be returned.
|
||||
// (e.g. if the home directory is /dev/null, *nix systems will
|
||||
// return ENOTDIR instead of ENOENT)
|
||||
return nil, errSourceUnavailable
|
||||
}
|
||||
|
||||
var metadata secureConnectMetadata
|
||||
if err := json.Unmarshal(file, &metadata); err != nil {
|
||||
return nil, fmt.Errorf("cert: could not parse JSON in %q: %w", configFilePath, err)
|
||||
}
|
||||
if err := validateMetadata(metadata); err != nil {
|
||||
return nil, fmt.Errorf("cert: invalid config in %q: %w", configFilePath, err)
|
||||
}
|
||||
return (&secureConnectSource{
|
||||
metadata: metadata,
|
||||
}).getClientCertificate, nil
|
||||
}
|
||||
|
||||
func validateMetadata(metadata secureConnectMetadata) error {
|
||||
if len(metadata.Cmd) == 0 {
|
||||
return errors.New("empty cert_provider_command")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *secureConnectSource) getClientCertificate(info *tls.CertificateRequestInfo) (*tls.Certificate, error) {
|
||||
s.cachedCertMutex.Lock()
|
||||
defer s.cachedCertMutex.Unlock()
|
||||
if s.cachedCert != nil && !isCertificateExpired(s.cachedCert) {
|
||||
return s.cachedCert, nil
|
||||
}
|
||||
// Expand OS environment variables in the cert provider command such as "$HOME".
|
||||
for i := 0; i < len(s.metadata.Cmd); i++ {
|
||||
s.metadata.Cmd[i] = os.ExpandEnv(s.metadata.Cmd[i])
|
||||
}
|
||||
command := s.metadata.Cmd
|
||||
data, err := exec.Command(command[0], command[1:]...).Output()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cert, err := tls.X509KeyPair(data, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.cachedCert = &cert
|
||||
return &cert, nil
|
||||
}
|
||||
|
||||
// isCertificateExpired returns true if the given cert is expired or invalid.
|
||||
func isCertificateExpired(cert *tls.Certificate) bool {
|
||||
if len(cert.Certificate) == 0 {
|
||||
return true
|
||||
}
|
||||
parsed, err := x509.ParseCertificate(cert.Certificate[0])
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
return time.Now().After(parsed.NotAfter)
|
||||
}
|
||||
114
src/server/vendor/cloud.google.com/go/auth/internal/transport/cert/workload_cert.go
generated
vendored
Normal file
114
src/server/vendor/cloud.google.com/go/auth/internal/transport/cert/workload_cert.go
generated
vendored
Normal file
@ -0,0 +1,114 @@
|
||||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cert
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/googleapis/enterprise-certificate-proxy/client/util"
|
||||
)
|
||||
|
||||
type certConfigs struct {
|
||||
Workload *workloadSource `json:"workload"`
|
||||
}
|
||||
|
||||
type workloadSource struct {
|
||||
CertPath string `json:"cert_path"`
|
||||
KeyPath string `json:"key_path"`
|
||||
}
|
||||
|
||||
type certificateConfig struct {
|
||||
CertConfigs certConfigs `json:"cert_configs"`
|
||||
}
|
||||
|
||||
// NewWorkloadX509CertProvider creates a certificate source
|
||||
// that reads a certificate and private key file from the local file system.
|
||||
// This is intended to be used for workload identity federation.
|
||||
//
|
||||
// The configFilePath points to a config file containing relevant parameters
|
||||
// such as the certificate and key file paths.
|
||||
// If configFilePath is empty, the client will attempt to load the config from
|
||||
// a well-known gcloud location.
|
||||
func NewWorkloadX509CertProvider(configFilePath string) (Provider, error) {
|
||||
if configFilePath == "" {
|
||||
envFilePath := util.GetConfigFilePathFromEnv()
|
||||
if envFilePath != "" {
|
||||
configFilePath = envFilePath
|
||||
} else {
|
||||
configFilePath = util.GetDefaultConfigFilePath()
|
||||
}
|
||||
}
|
||||
|
||||
certFile, keyFile, err := getCertAndKeyFiles(configFilePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
source := &workloadSource{
|
||||
CertPath: certFile,
|
||||
KeyPath: keyFile,
|
||||
}
|
||||
return source.getClientCertificate, nil
|
||||
}
|
||||
|
||||
// getClientCertificate attempts to load the certificate and key from the files specified in the
|
||||
// certificate config.
|
||||
func (s *workloadSource) getClientCertificate(info *tls.CertificateRequestInfo) (*tls.Certificate, error) {
|
||||
cert, err := tls.LoadX509KeyPair(s.CertPath, s.KeyPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &cert, nil
|
||||
}
|
||||
|
||||
// getCertAndKeyFiles attempts to read the provided config file and return the certificate and private
|
||||
// key file paths.
|
||||
func getCertAndKeyFiles(configFilePath string) (string, string, error) {
|
||||
jsonFile, err := os.Open(configFilePath)
|
||||
if err != nil {
|
||||
return "", "", errSourceUnavailable
|
||||
}
|
||||
|
||||
byteValue, err := io.ReadAll(jsonFile)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
var config certificateConfig
|
||||
if err := json.Unmarshal(byteValue, &config); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
if config.CertConfigs.Workload == nil {
|
||||
return "", "", errSourceUnavailable
|
||||
}
|
||||
|
||||
certFile := config.CertConfigs.Workload.CertPath
|
||||
keyFile := config.CertConfigs.Workload.KeyPath
|
||||
|
||||
if certFile == "" {
|
||||
return "", "", errors.New("certificate configuration is missing the certificate file location")
|
||||
}
|
||||
|
||||
if keyFile == "" {
|
||||
return "", "", errors.New("certificate configuration is missing the key file location")
|
||||
}
|
||||
|
||||
return certFile, keyFile, nil
|
||||
}
|
||||
138
src/server/vendor/cloud.google.com/go/auth/internal/transport/s2a.go
generated
vendored
Normal file
138
src/server/vendor/cloud.google.com/go/auth/internal/transport/s2a.go
generated
vendored
Normal file
@ -0,0 +1,138 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package transport
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"log/slog"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"cloud.google.com/go/auth/internal/transport/cert"
|
||||
"cloud.google.com/go/compute/metadata"
|
||||
)
|
||||
|
||||
const (
|
||||
configEndpointSuffix = "instance/platform-security/auto-mtls-configuration"
|
||||
)
|
||||
|
||||
var (
|
||||
mtlsConfiguration *mtlsConfig
|
||||
|
||||
mtlsOnce sync.Once
|
||||
)
|
||||
|
||||
// GetS2AAddress returns the S2A address to be reached via plaintext connection.
|
||||
// Returns empty string if not set or invalid.
|
||||
func GetS2AAddress(logger *slog.Logger) string {
|
||||
getMetadataMTLSAutoConfig(logger)
|
||||
if !mtlsConfiguration.valid() {
|
||||
return ""
|
||||
}
|
||||
return mtlsConfiguration.S2A.PlaintextAddress
|
||||
}
|
||||
|
||||
// GetMTLSS2AAddress returns the S2A address to be reached via MTLS connection.
|
||||
// Returns empty string if not set or invalid.
|
||||
func GetMTLSS2AAddress(logger *slog.Logger) string {
|
||||
getMetadataMTLSAutoConfig(logger)
|
||||
if !mtlsConfiguration.valid() {
|
||||
return ""
|
||||
}
|
||||
return mtlsConfiguration.S2A.MTLSAddress
|
||||
}
|
||||
|
||||
// mtlsConfig contains the configuration for establishing MTLS connections with Google APIs.
|
||||
type mtlsConfig struct {
|
||||
S2A *s2aAddresses `json:"s2a"`
|
||||
}
|
||||
|
||||
func (c *mtlsConfig) valid() bool {
|
||||
return c != nil && c.S2A != nil
|
||||
}
|
||||
|
||||
// s2aAddresses contains the plaintext and/or MTLS S2A addresses.
|
||||
type s2aAddresses struct {
|
||||
// PlaintextAddress is the plaintext address to reach S2A
|
||||
PlaintextAddress string `json:"plaintext_address"`
|
||||
// MTLSAddress is the MTLS address to reach S2A
|
||||
MTLSAddress string `json:"mtls_address"`
|
||||
}
|
||||
|
||||
func getMetadataMTLSAutoConfig(logger *slog.Logger) {
|
||||
var err error
|
||||
mtlsOnce.Do(func() {
|
||||
mtlsConfiguration, err = queryConfig(logger)
|
||||
if err != nil {
|
||||
log.Printf("Getting MTLS config failed: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var httpGetMetadataMTLSConfig = func(logger *slog.Logger) (string, error) {
|
||||
metadataClient := metadata.NewWithOptions(&metadata.Options{
|
||||
Logger: logger,
|
||||
})
|
||||
return metadataClient.GetWithContext(context.Background(), configEndpointSuffix)
|
||||
}
|
||||
|
||||
func queryConfig(logger *slog.Logger) (*mtlsConfig, error) {
|
||||
resp, err := httpGetMetadataMTLSConfig(logger)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("querying MTLS config from MDS endpoint failed: %w", err)
|
||||
}
|
||||
var config mtlsConfig
|
||||
err = json.Unmarshal([]byte(resp), &config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unmarshalling MTLS config from MDS endpoint failed: %w", err)
|
||||
}
|
||||
if config.S2A == nil {
|
||||
return nil, fmt.Errorf("returned MTLS config from MDS endpoint is invalid: %v", config)
|
||||
}
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
func shouldUseS2A(clientCertSource cert.Provider, opts *Options) bool {
|
||||
// If client cert is found, use that over S2A.
|
||||
if clientCertSource != nil {
|
||||
return false
|
||||
}
|
||||
// If EXPERIMENTAL_GOOGLE_API_USE_S2A is not set to true, skip S2A.
|
||||
if !isGoogleS2AEnabled() {
|
||||
return false
|
||||
}
|
||||
// If DefaultMTLSEndpoint is not set or has endpoint override, skip S2A.
|
||||
if opts.DefaultMTLSEndpoint == "" || opts.Endpoint != "" {
|
||||
return false
|
||||
}
|
||||
// If custom HTTP client is provided, skip S2A.
|
||||
if opts.Client != nil {
|
||||
return false
|
||||
}
|
||||
// If directPath is enabled, skip S2A.
|
||||
return !opts.EnableDirectPath && !opts.EnableDirectPathXds
|
||||
}
|
||||
|
||||
func isGoogleS2AEnabled() bool {
|
||||
b, err := strconv.ParseBool(os.Getenv(googleAPIUseS2AEnv))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return b
|
||||
}
|
||||
106
src/server/vendor/cloud.google.com/go/auth/internal/transport/transport.go
generated
vendored
Normal file
106
src/server/vendor/cloud.google.com/go/auth/internal/transport/transport.go
generated
vendored
Normal file
@ -0,0 +1,106 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package transport provided internal helpers for the two transport packages
|
||||
// (grpctransport and httptransport).
|
||||
package transport
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/auth/credentials"
|
||||
)
|
||||
|
||||
// CloneDetectOptions clones a user set detect option into some new memory that
|
||||
// we can internally manipulate before sending onto the detect package.
|
||||
func CloneDetectOptions(oldDo *credentials.DetectOptions) *credentials.DetectOptions {
|
||||
if oldDo == nil {
|
||||
// it is valid for users not to set this, but we will need to to default
|
||||
// some options for them in this case so return some initialized memory
|
||||
// to work with.
|
||||
return &credentials.DetectOptions{}
|
||||
}
|
||||
newDo := &credentials.DetectOptions{
|
||||
// Simple types
|
||||
Audience: oldDo.Audience,
|
||||
Subject: oldDo.Subject,
|
||||
EarlyTokenRefresh: oldDo.EarlyTokenRefresh,
|
||||
TokenURL: oldDo.TokenURL,
|
||||
STSAudience: oldDo.STSAudience,
|
||||
CredentialsFile: oldDo.CredentialsFile,
|
||||
UseSelfSignedJWT: oldDo.UseSelfSignedJWT,
|
||||
UniverseDomain: oldDo.UniverseDomain,
|
||||
|
||||
// These fields are are pointer types that we just want to use exactly
|
||||
// as the user set, copy the ref
|
||||
Client: oldDo.Client,
|
||||
Logger: oldDo.Logger,
|
||||
AuthHandlerOptions: oldDo.AuthHandlerOptions,
|
||||
}
|
||||
|
||||
// Smartly size this memory and copy below.
|
||||
if len(oldDo.CredentialsJSON) > 0 {
|
||||
newDo.CredentialsJSON = make([]byte, len(oldDo.CredentialsJSON))
|
||||
copy(newDo.CredentialsJSON, oldDo.CredentialsJSON)
|
||||
}
|
||||
if len(oldDo.Scopes) > 0 {
|
||||
newDo.Scopes = make([]string, len(oldDo.Scopes))
|
||||
copy(newDo.Scopes, oldDo.Scopes)
|
||||
}
|
||||
|
||||
return newDo
|
||||
}
|
||||
|
||||
// ValidateUniverseDomain verifies that the universe domain configured for the
|
||||
// client matches the universe domain configured for the credentials.
|
||||
func ValidateUniverseDomain(clientUniverseDomain, credentialsUniverseDomain string) error {
|
||||
if clientUniverseDomain != credentialsUniverseDomain {
|
||||
return fmt.Errorf(
|
||||
"the configured universe domain (%q) does not match the universe "+
|
||||
"domain found in the credentials (%q). If you haven't configured "+
|
||||
"the universe domain explicitly, \"googleapis.com\" is the default",
|
||||
clientUniverseDomain,
|
||||
credentialsUniverseDomain)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DefaultHTTPClientWithTLS constructs an HTTPClient using the provided tlsConfig, to support mTLS.
|
||||
func DefaultHTTPClientWithTLS(tlsConfig *tls.Config) *http.Client {
|
||||
trans := BaseTransport()
|
||||
trans.TLSClientConfig = tlsConfig
|
||||
return &http.Client{Transport: trans}
|
||||
}
|
||||
|
||||
// BaseTransport returns a default [http.Transport] which can be used if
|
||||
// [http.DefaultTransport] has been overwritten.
|
||||
func BaseTransport() *http.Transport {
|
||||
return &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
DualStack: true,
|
||||
}).DialContext,
|
||||
MaxIdleConns: 100,
|
||||
MaxIdleConnsPerHost: 100,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
}
|
||||
}
|
||||
75
src/server/vendor/cloud.google.com/go/auth/oauth2adapt/CHANGES.md
generated
vendored
Normal file
75
src/server/vendor/cloud.google.com/go/auth/oauth2adapt/CHANGES.md
generated
vendored
Normal file
@ -0,0 +1,75 @@
|
||||
# Changelog
|
||||
|
||||
## [0.2.7](https://github.com/googleapis/google-cloud-go/compare/auth/oauth2adapt/v0.2.6...auth/oauth2adapt/v0.2.7) (2025-01-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth/oauth2adapt:** Update golang.org/x/net to v0.33.0 ([e9b0b69](https://github.com/googleapis/google-cloud-go/commit/e9b0b69644ea5b276cacff0a707e8a5e87efafc9))
|
||||
|
||||
## [0.2.6](https://github.com/googleapis/google-cloud-go/compare/auth/oauth2adapt/v0.2.5...auth/oauth2adapt/v0.2.6) (2024-11-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth/oauth2adapt:** Copy map in tokenSourceAdapter.Token ([#11164](https://github.com/googleapis/google-cloud-go/issues/11164)) ([8cb0cbc](https://github.com/googleapis/google-cloud-go/commit/8cb0cbccdc32886dfb3af49fee04012937d114d2)), refs [#11161](https://github.com/googleapis/google-cloud-go/issues/11161)
|
||||
|
||||
## [0.2.5](https://github.com/googleapis/google-cloud-go/compare/auth/oauth2adapt/v0.2.4...auth/oauth2adapt/v0.2.5) (2024-10-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth/oauth2adapt:** Convert token metadata where possible ([#11062](https://github.com/googleapis/google-cloud-go/issues/11062)) ([34bf1c1](https://github.com/googleapis/google-cloud-go/commit/34bf1c164465d66745c0cfdf7cd10a8e2da92e52))
|
||||
|
||||
## [0.2.4](https://github.com/googleapis/google-cloud-go/compare/auth/oauth2adapt/v0.2.3...auth/oauth2adapt/v0.2.4) (2024-08-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth/oauth2adapt:** Update dependencies ([257c40b](https://github.com/googleapis/google-cloud-go/commit/257c40bd6d7e59730017cf32bda8823d7a232758))
|
||||
|
||||
## [0.2.3](https://github.com/googleapis/google-cloud-go/compare/auth/oauth2adapt/v0.2.2...auth/oauth2adapt/v0.2.3) (2024-07-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth/oauth2adapt:** Bump google.golang.org/api@v0.187.0 ([8fa9e39](https://github.com/googleapis/google-cloud-go/commit/8fa9e398e512fd8533fd49060371e61b5725a85b))
|
||||
|
||||
## [0.2.2](https://github.com/googleapis/google-cloud-go/compare/auth/oauth2adapt/v0.2.1...auth/oauth2adapt/v0.2.2) (2024-04-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth/oauth2adapt:** Bump x/net to v0.24.0 ([ba31ed5](https://github.com/googleapis/google-cloud-go/commit/ba31ed5fda2c9664f2e1cf972469295e63deb5b4))
|
||||
|
||||
## [0.2.1](https://github.com/googleapis/google-cloud-go/compare/auth/oauth2adapt/v0.2.0...auth/oauth2adapt/v0.2.1) (2024-04-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth/oauth2adapt:** Adapt Token Types to be translated ([#9801](https://github.com/googleapis/google-cloud-go/issues/9801)) ([70f4115](https://github.com/googleapis/google-cloud-go/commit/70f411555ebbf2b71e6d425cc8d2030644c6b438)), refs [#9800](https://github.com/googleapis/google-cloud-go/issues/9800)
|
||||
|
||||
## [0.2.0](https://github.com/googleapis/google-cloud-go/compare/auth/oauth2adapt/v0.1.0...auth/oauth2adapt/v0.2.0) (2024-04-16)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **auth/oauth2adapt:** Add helpers for working with credentials types ([#9694](https://github.com/googleapis/google-cloud-go/issues/9694)) ([cf33b55](https://github.com/googleapis/google-cloud-go/commit/cf33b5514423a2ac5c2a323a1cd99aac34fd4233))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth/oauth2adapt:** Update protobuf dep to v1.33.0 ([30b038d](https://github.com/googleapis/google-cloud-go/commit/30b038d8cac0b8cd5dd4761c87f3f298760dd33a))
|
||||
|
||||
## 0.1.0 (2023-10-19)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **auth/oauth2adapt:** Adds a new module to translate types ([#8595](https://github.com/googleapis/google-cloud-go/issues/8595)) ([6933c5a](https://github.com/googleapis/google-cloud-go/commit/6933c5a0c1fc8e58cbfff8bbca439d671b94672f))
|
||||
* **auth/oauth2adapt:** Fixup deps for release ([#8747](https://github.com/googleapis/google-cloud-go/issues/8747)) ([749d243](https://github.com/googleapis/google-cloud-go/commit/749d243862b025a6487a4d2d339219889b4cfe70))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth/oauth2adapt:** Update golang.org/x/net to v0.17.0 ([174da47](https://github.com/googleapis/google-cloud-go/commit/174da47254fefb12921bbfc65b7829a453af6f5d))
|
||||
202
src/server/vendor/cloud.google.com/go/auth/oauth2adapt/LICENSE
generated
vendored
Normal file
202
src/server/vendor/cloud.google.com/go/auth/oauth2adapt/LICENSE
generated
vendored
Normal file
@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
200
src/server/vendor/cloud.google.com/go/auth/oauth2adapt/oauth2adapt.go
generated
vendored
Normal file
200
src/server/vendor/cloud.google.com/go/auth/oauth2adapt/oauth2adapt.go
generated
vendored
Normal file
@ -0,0 +1,200 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package oauth2adapt helps converts types used in [cloud.google.com/go/auth]
|
||||
// and [golang.org/x/oauth2].
|
||||
package oauth2adapt
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"cloud.google.com/go/auth"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/google"
|
||||
)
|
||||
|
||||
const (
|
||||
oauth2TokenSourceKey = "oauth2.google.tokenSource"
|
||||
oauth2ServiceAccountKey = "oauth2.google.serviceAccount"
|
||||
authTokenSourceKey = "auth.google.tokenSource"
|
||||
authServiceAccountKey = "auth.google.serviceAccount"
|
||||
)
|
||||
|
||||
// TokenProviderFromTokenSource converts any [golang.org/x/oauth2.TokenSource]
|
||||
// into a [cloud.google.com/go/auth.TokenProvider].
|
||||
func TokenProviderFromTokenSource(ts oauth2.TokenSource) auth.TokenProvider {
|
||||
return &tokenProviderAdapter{ts: ts}
|
||||
}
|
||||
|
||||
type tokenProviderAdapter struct {
|
||||
ts oauth2.TokenSource
|
||||
}
|
||||
|
||||
// Token fulfills the [cloud.google.com/go/auth.TokenProvider] interface. It
|
||||
// is a light wrapper around the underlying TokenSource.
|
||||
func (tp *tokenProviderAdapter) Token(context.Context) (*auth.Token, error) {
|
||||
tok, err := tp.ts.Token()
|
||||
if err != nil {
|
||||
var err2 *oauth2.RetrieveError
|
||||
if ok := errors.As(err, &err2); ok {
|
||||
return nil, AuthErrorFromRetrieveError(err2)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
// Preserve compute token metadata, for both types of tokens.
|
||||
metadata := map[string]interface{}{}
|
||||
if val, ok := tok.Extra(oauth2TokenSourceKey).(string); ok {
|
||||
metadata[authTokenSourceKey] = val
|
||||
metadata[oauth2TokenSourceKey] = val
|
||||
}
|
||||
if val, ok := tok.Extra(oauth2ServiceAccountKey).(string); ok {
|
||||
metadata[authServiceAccountKey] = val
|
||||
metadata[oauth2ServiceAccountKey] = val
|
||||
}
|
||||
return &auth.Token{
|
||||
Value: tok.AccessToken,
|
||||
Type: tok.Type(),
|
||||
Expiry: tok.Expiry,
|
||||
Metadata: metadata,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// TokenSourceFromTokenProvider converts any
|
||||
// [cloud.google.com/go/auth.TokenProvider] into a
|
||||
// [golang.org/x/oauth2.TokenSource].
|
||||
func TokenSourceFromTokenProvider(tp auth.TokenProvider) oauth2.TokenSource {
|
||||
return &tokenSourceAdapter{tp: tp}
|
||||
}
|
||||
|
||||
type tokenSourceAdapter struct {
|
||||
tp auth.TokenProvider
|
||||
}
|
||||
|
||||
// Token fulfills the [golang.org/x/oauth2.TokenSource] interface. It
|
||||
// is a light wrapper around the underlying TokenProvider.
|
||||
func (ts *tokenSourceAdapter) Token() (*oauth2.Token, error) {
|
||||
tok, err := ts.tp.Token(context.Background())
|
||||
if err != nil {
|
||||
var err2 *auth.Error
|
||||
if ok := errors.As(err, &err2); ok {
|
||||
return nil, AddRetrieveErrorToAuthError(err2)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
tok2 := &oauth2.Token{
|
||||
AccessToken: tok.Value,
|
||||
TokenType: tok.Type,
|
||||
Expiry: tok.Expiry,
|
||||
}
|
||||
// Preserve token metadata.
|
||||
m := tok.Metadata
|
||||
if m != nil {
|
||||
// Copy map to avoid concurrent map writes error (#11161).
|
||||
metadata := make(map[string]interface{}, len(m)+2)
|
||||
for k, v := range m {
|
||||
metadata[k] = v
|
||||
}
|
||||
// Append compute token metadata in converted form.
|
||||
if val, ok := metadata[authTokenSourceKey].(string); ok && val != "" {
|
||||
metadata[oauth2TokenSourceKey] = val
|
||||
}
|
||||
if val, ok := metadata[authServiceAccountKey].(string); ok && val != "" {
|
||||
metadata[oauth2ServiceAccountKey] = val
|
||||
}
|
||||
tok2 = tok2.WithExtra(metadata)
|
||||
}
|
||||
return tok2, nil
|
||||
}
|
||||
|
||||
// AuthCredentialsFromOauth2Credentials converts a [golang.org/x/oauth2/google.Credentials]
|
||||
// to a [cloud.google.com/go/auth.Credentials].
|
||||
func AuthCredentialsFromOauth2Credentials(creds *google.Credentials) *auth.Credentials {
|
||||
if creds == nil {
|
||||
return nil
|
||||
}
|
||||
return auth.NewCredentials(&auth.CredentialsOptions{
|
||||
TokenProvider: TokenProviderFromTokenSource(creds.TokenSource),
|
||||
JSON: creds.JSON,
|
||||
ProjectIDProvider: auth.CredentialsPropertyFunc(func(ctx context.Context) (string, error) {
|
||||
return creds.ProjectID, nil
|
||||
}),
|
||||
UniverseDomainProvider: auth.CredentialsPropertyFunc(func(ctx context.Context) (string, error) {
|
||||
return creds.GetUniverseDomain()
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
// Oauth2CredentialsFromAuthCredentials converts a [cloud.google.com/go/auth.Credentials]
|
||||
// to a [golang.org/x/oauth2/google.Credentials].
|
||||
func Oauth2CredentialsFromAuthCredentials(creds *auth.Credentials) *google.Credentials {
|
||||
if creds == nil {
|
||||
return nil
|
||||
}
|
||||
// Throw away errors as old credentials are not request aware. Also, no
|
||||
// network requests are currently happening for this use case.
|
||||
projectID, _ := creds.ProjectID(context.Background())
|
||||
|
||||
return &google.Credentials{
|
||||
TokenSource: TokenSourceFromTokenProvider(creds.TokenProvider),
|
||||
ProjectID: projectID,
|
||||
JSON: creds.JSON(),
|
||||
UniverseDomainProvider: func() (string, error) {
|
||||
return creds.UniverseDomain(context.Background())
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type oauth2Error struct {
|
||||
ErrorCode string `json:"error"`
|
||||
ErrorDescription string `json:"error_description"`
|
||||
ErrorURI string `json:"error_uri"`
|
||||
}
|
||||
|
||||
// AddRetrieveErrorToAuthError returns the same error provided and adds a
|
||||
// [golang.org/x/oauth2.RetrieveError] to the error chain by setting the `Err` field on the
|
||||
// [cloud.google.com/go/auth.Error].
|
||||
func AddRetrieveErrorToAuthError(err *auth.Error) *auth.Error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
e := &oauth2.RetrieveError{
|
||||
Response: err.Response,
|
||||
Body: err.Body,
|
||||
}
|
||||
err.Err = e
|
||||
if len(err.Body) > 0 {
|
||||
var oErr oauth2Error
|
||||
// ignore the error as it only fills in extra details
|
||||
json.Unmarshal(err.Body, &oErr)
|
||||
e.ErrorCode = oErr.ErrorCode
|
||||
e.ErrorDescription = oErr.ErrorDescription
|
||||
e.ErrorURI = oErr.ErrorURI
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// AuthErrorFromRetrieveError returns an [cloud.google.com/go/auth.Error] that
|
||||
// wraps the provided [golang.org/x/oauth2.RetrieveError].
|
||||
func AuthErrorFromRetrieveError(err *oauth2.RetrieveError) *auth.Error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return &auth.Error{
|
||||
Response: err.Response,
|
||||
Body: err.Body,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
382
src/server/vendor/cloud.google.com/go/auth/threelegged.go
generated
vendored
Normal file
382
src/server/vendor/cloud.google.com/go/auth/threelegged.go
generated
vendored
Normal file
@ -0,0 +1,382 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"mime"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/auth/internal"
|
||||
"github.com/googleapis/gax-go/v2/internallog"
|
||||
)
|
||||
|
||||
// AuthorizationHandler is a 3-legged-OAuth helper that prompts the user for
|
||||
// OAuth consent at the specified auth code URL and returns an auth code and
|
||||
// state upon approval.
|
||||
type AuthorizationHandler func(authCodeURL string) (code string, state string, err error)
|
||||
|
||||
// Options3LO are the options for doing a 3-legged OAuth2 flow.
|
||||
type Options3LO struct {
|
||||
// ClientID is the application's ID.
|
||||
ClientID string
|
||||
// ClientSecret is the application's secret. Not required if AuthHandlerOpts
|
||||
// is set.
|
||||
ClientSecret string
|
||||
// AuthURL is the URL for authenticating.
|
||||
AuthURL string
|
||||
// TokenURL is the URL for retrieving a token.
|
||||
TokenURL string
|
||||
// AuthStyle is used to describe how to client info in the token request.
|
||||
AuthStyle Style
|
||||
// RefreshToken is the token used to refresh the credential. Not required
|
||||
// if AuthHandlerOpts is set.
|
||||
RefreshToken string
|
||||
// RedirectURL is the URL to redirect users to. Optional.
|
||||
RedirectURL string
|
||||
// Scopes specifies requested permissions for the Token. Optional.
|
||||
Scopes []string
|
||||
|
||||
// URLParams are the set of values to apply to the token exchange. Optional.
|
||||
URLParams url.Values
|
||||
// Client is the client to be used to make the underlying token requests.
|
||||
// Optional.
|
||||
Client *http.Client
|
||||
// EarlyTokenExpiry is the time before the token expires that it should be
|
||||
// refreshed. If not set the default value is 3 minutes and 45 seconds.
|
||||
// Optional.
|
||||
EarlyTokenExpiry time.Duration
|
||||
|
||||
// AuthHandlerOpts provides a set of options for doing a
|
||||
// 3-legged OAuth2 flow with a custom [AuthorizationHandler]. Optional.
|
||||
AuthHandlerOpts *AuthorizationHandlerOptions
|
||||
// Logger is used for debug logging. If provided, logging will be enabled
|
||||
// at the loggers configured level. By default logging is disabled unless
|
||||
// enabled by setting GOOGLE_SDK_GO_LOGGING_LEVEL in which case a default
|
||||
// logger will be used. Optional.
|
||||
Logger *slog.Logger
|
||||
}
|
||||
|
||||
func (o *Options3LO) validate() error {
|
||||
if o == nil {
|
||||
return errors.New("auth: options must be provided")
|
||||
}
|
||||
if o.ClientID == "" {
|
||||
return errors.New("auth: client ID must be provided")
|
||||
}
|
||||
if o.AuthHandlerOpts == nil && o.ClientSecret == "" {
|
||||
return errors.New("auth: client secret must be provided")
|
||||
}
|
||||
if o.AuthURL == "" {
|
||||
return errors.New("auth: auth URL must be provided")
|
||||
}
|
||||
if o.TokenURL == "" {
|
||||
return errors.New("auth: token URL must be provided")
|
||||
}
|
||||
if o.AuthStyle == StyleUnknown {
|
||||
return errors.New("auth: auth style must be provided")
|
||||
}
|
||||
if o.AuthHandlerOpts == nil && o.RefreshToken == "" {
|
||||
return errors.New("auth: refresh token must be provided")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Options3LO) logger() *slog.Logger {
|
||||
return internallog.New(o.Logger)
|
||||
}
|
||||
|
||||
// PKCEOptions holds parameters to support PKCE.
|
||||
type PKCEOptions struct {
|
||||
// Challenge is the un-padded, base64-url-encoded string of the encrypted code verifier.
|
||||
Challenge string // The un-padded, base64-url-encoded string of the encrypted code verifier.
|
||||
// ChallengeMethod is the encryption method (ex. S256).
|
||||
ChallengeMethod string
|
||||
// Verifier is the original, non-encrypted secret.
|
||||
Verifier string // The original, non-encrypted secret.
|
||||
}
|
||||
|
||||
type tokenJSON struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
TokenType string `json:"token_type"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
// error fields
|
||||
ErrorCode string `json:"error"`
|
||||
ErrorDescription string `json:"error_description"`
|
||||
ErrorURI string `json:"error_uri"`
|
||||
}
|
||||
|
||||
func (e *tokenJSON) expiry() (t time.Time) {
|
||||
if v := e.ExpiresIn; v != 0 {
|
||||
return time.Now().Add(time.Duration(v) * time.Second)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *Options3LO) client() *http.Client {
|
||||
if o.Client != nil {
|
||||
return o.Client
|
||||
}
|
||||
return internal.DefaultClient()
|
||||
}
|
||||
|
||||
// authCodeURL returns a URL that points to a OAuth2 consent page.
|
||||
func (o *Options3LO) authCodeURL(state string, values url.Values) string {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString(o.AuthURL)
|
||||
v := url.Values{
|
||||
"response_type": {"code"},
|
||||
"client_id": {o.ClientID},
|
||||
}
|
||||
if o.RedirectURL != "" {
|
||||
v.Set("redirect_uri", o.RedirectURL)
|
||||
}
|
||||
if len(o.Scopes) > 0 {
|
||||
v.Set("scope", strings.Join(o.Scopes, " "))
|
||||
}
|
||||
if state != "" {
|
||||
v.Set("state", state)
|
||||
}
|
||||
if o.AuthHandlerOpts != nil {
|
||||
if o.AuthHandlerOpts.PKCEOpts != nil &&
|
||||
o.AuthHandlerOpts.PKCEOpts.Challenge != "" {
|
||||
v.Set(codeChallengeKey, o.AuthHandlerOpts.PKCEOpts.Challenge)
|
||||
}
|
||||
if o.AuthHandlerOpts.PKCEOpts != nil &&
|
||||
o.AuthHandlerOpts.PKCEOpts.ChallengeMethod != "" {
|
||||
v.Set(codeChallengeMethodKey, o.AuthHandlerOpts.PKCEOpts.ChallengeMethod)
|
||||
}
|
||||
}
|
||||
for k := range values {
|
||||
v.Set(k, v.Get(k))
|
||||
}
|
||||
if strings.Contains(o.AuthURL, "?") {
|
||||
buf.WriteByte('&')
|
||||
} else {
|
||||
buf.WriteByte('?')
|
||||
}
|
||||
buf.WriteString(v.Encode())
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// New3LOTokenProvider returns a [TokenProvider] based on the 3-legged OAuth2
|
||||
// configuration. The TokenProvider is caches and auto-refreshes tokens by
|
||||
// default.
|
||||
func New3LOTokenProvider(opts *Options3LO) (TokenProvider, error) {
|
||||
if err := opts.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if opts.AuthHandlerOpts != nil {
|
||||
return new3LOTokenProviderWithAuthHandler(opts), nil
|
||||
}
|
||||
return NewCachedTokenProvider(&tokenProvider3LO{opts: opts, refreshToken: opts.RefreshToken, client: opts.client()}, &CachedTokenProviderOptions{
|
||||
ExpireEarly: opts.EarlyTokenExpiry,
|
||||
}), nil
|
||||
}
|
||||
|
||||
// AuthorizationHandlerOptions provides a set of options to specify for doing a
|
||||
// 3-legged OAuth2 flow with a custom [AuthorizationHandler].
|
||||
type AuthorizationHandlerOptions struct {
|
||||
// AuthorizationHandler specifies the handler used to for the authorization
|
||||
// part of the flow.
|
||||
Handler AuthorizationHandler
|
||||
// State is used verify that the "state" is identical in the request and
|
||||
// response before exchanging the auth code for OAuth2 token.
|
||||
State string
|
||||
// PKCEOpts allows setting configurations for PKCE. Optional.
|
||||
PKCEOpts *PKCEOptions
|
||||
}
|
||||
|
||||
func new3LOTokenProviderWithAuthHandler(opts *Options3LO) TokenProvider {
|
||||
return NewCachedTokenProvider(&tokenProviderWithHandler{opts: opts, state: opts.AuthHandlerOpts.State}, &CachedTokenProviderOptions{
|
||||
ExpireEarly: opts.EarlyTokenExpiry,
|
||||
})
|
||||
}
|
||||
|
||||
// exchange handles the final exchange portion of the 3lo flow. Returns a Token,
|
||||
// refreshToken, and error.
|
||||
func (o *Options3LO) exchange(ctx context.Context, code string) (*Token, string, error) {
|
||||
// Build request
|
||||
v := url.Values{
|
||||
"grant_type": {"authorization_code"},
|
||||
"code": {code},
|
||||
}
|
||||
if o.RedirectURL != "" {
|
||||
v.Set("redirect_uri", o.RedirectURL)
|
||||
}
|
||||
if o.AuthHandlerOpts != nil &&
|
||||
o.AuthHandlerOpts.PKCEOpts != nil &&
|
||||
o.AuthHandlerOpts.PKCEOpts.Verifier != "" {
|
||||
v.Set(codeVerifierKey, o.AuthHandlerOpts.PKCEOpts.Verifier)
|
||||
}
|
||||
for k := range o.URLParams {
|
||||
v.Set(k, o.URLParams.Get(k))
|
||||
}
|
||||
return fetchToken(ctx, o, v)
|
||||
}
|
||||
|
||||
// This struct is not safe for concurrent access alone, but the way it is used
|
||||
// in this package by wrapping it with a cachedTokenProvider makes it so.
|
||||
type tokenProvider3LO struct {
|
||||
opts *Options3LO
|
||||
client *http.Client
|
||||
refreshToken string
|
||||
}
|
||||
|
||||
func (tp *tokenProvider3LO) Token(ctx context.Context) (*Token, error) {
|
||||
if tp.refreshToken == "" {
|
||||
return nil, errors.New("auth: token expired and refresh token is not set")
|
||||
}
|
||||
v := url.Values{
|
||||
"grant_type": {"refresh_token"},
|
||||
"refresh_token": {tp.refreshToken},
|
||||
}
|
||||
for k := range tp.opts.URLParams {
|
||||
v.Set(k, tp.opts.URLParams.Get(k))
|
||||
}
|
||||
|
||||
tk, rt, err := fetchToken(ctx, tp.opts, v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tp.refreshToken != rt && rt != "" {
|
||||
tp.refreshToken = rt
|
||||
}
|
||||
return tk, err
|
||||
}
|
||||
|
||||
type tokenProviderWithHandler struct {
|
||||
opts *Options3LO
|
||||
state string
|
||||
}
|
||||
|
||||
func (tp tokenProviderWithHandler) Token(ctx context.Context) (*Token, error) {
|
||||
url := tp.opts.authCodeURL(tp.state, nil)
|
||||
code, state, err := tp.opts.AuthHandlerOpts.Handler(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if state != tp.state {
|
||||
return nil, errors.New("auth: state mismatch in 3-legged-OAuth flow")
|
||||
}
|
||||
tok, _, err := tp.opts.exchange(ctx, code)
|
||||
return tok, err
|
||||
}
|
||||
|
||||
// fetchToken returns a Token, refresh token, and/or an error.
|
||||
func fetchToken(ctx context.Context, o *Options3LO, v url.Values) (*Token, string, error) {
|
||||
var refreshToken string
|
||||
if o.AuthStyle == StyleInParams {
|
||||
if o.ClientID != "" {
|
||||
v.Set("client_id", o.ClientID)
|
||||
}
|
||||
if o.ClientSecret != "" {
|
||||
v.Set("client_secret", o.ClientSecret)
|
||||
}
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", o.TokenURL, strings.NewReader(v.Encode()))
|
||||
if err != nil {
|
||||
return nil, refreshToken, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
if o.AuthStyle == StyleInHeader {
|
||||
req.SetBasicAuth(url.QueryEscape(o.ClientID), url.QueryEscape(o.ClientSecret))
|
||||
}
|
||||
logger := o.logger()
|
||||
|
||||
logger.DebugContext(ctx, "3LO token request", "request", internallog.HTTPRequest(req, []byte(v.Encode())))
|
||||
// Make request
|
||||
resp, body, err := internal.DoRequest(o.client(), req)
|
||||
if err != nil {
|
||||
return nil, refreshToken, err
|
||||
}
|
||||
logger.DebugContext(ctx, "3LO token response", "response", internallog.HTTPResponse(resp, body))
|
||||
failureStatus := resp.StatusCode < 200 || resp.StatusCode > 299
|
||||
tokError := &Error{
|
||||
Response: resp,
|
||||
Body: body,
|
||||
}
|
||||
|
||||
var token *Token
|
||||
// errors ignored because of default switch on content
|
||||
content, _, _ := mime.ParseMediaType(resp.Header.Get("Content-Type"))
|
||||
switch content {
|
||||
case "application/x-www-form-urlencoded", "text/plain":
|
||||
// some endpoints return a query string
|
||||
vals, err := url.ParseQuery(string(body))
|
||||
if err != nil {
|
||||
if failureStatus {
|
||||
return nil, refreshToken, tokError
|
||||
}
|
||||
return nil, refreshToken, fmt.Errorf("auth: cannot parse response: %w", err)
|
||||
}
|
||||
tokError.code = vals.Get("error")
|
||||
tokError.description = vals.Get("error_description")
|
||||
tokError.uri = vals.Get("error_uri")
|
||||
token = &Token{
|
||||
Value: vals.Get("access_token"),
|
||||
Type: vals.Get("token_type"),
|
||||
Metadata: make(map[string]interface{}, len(vals)),
|
||||
}
|
||||
for k, v := range vals {
|
||||
token.Metadata[k] = v
|
||||
}
|
||||
refreshToken = vals.Get("refresh_token")
|
||||
e := vals.Get("expires_in")
|
||||
expires, _ := strconv.Atoi(e)
|
||||
if expires != 0 {
|
||||
token.Expiry = time.Now().Add(time.Duration(expires) * time.Second)
|
||||
}
|
||||
default:
|
||||
var tj tokenJSON
|
||||
if err = json.Unmarshal(body, &tj); err != nil {
|
||||
if failureStatus {
|
||||
return nil, refreshToken, tokError
|
||||
}
|
||||
return nil, refreshToken, fmt.Errorf("auth: cannot parse json: %w", err)
|
||||
}
|
||||
tokError.code = tj.ErrorCode
|
||||
tokError.description = tj.ErrorDescription
|
||||
tokError.uri = tj.ErrorURI
|
||||
token = &Token{
|
||||
Value: tj.AccessToken,
|
||||
Type: tj.TokenType,
|
||||
Expiry: tj.expiry(),
|
||||
Metadata: make(map[string]interface{}),
|
||||
}
|
||||
json.Unmarshal(body, &token.Metadata) // optional field, skip err check
|
||||
refreshToken = tj.RefreshToken
|
||||
}
|
||||
// according to spec, servers should respond status 400 in error case
|
||||
// https://www.rfc-editor.org/rfc/rfc6749#section-5.2
|
||||
// but some unorthodox servers respond 200 in error case
|
||||
if failureStatus || tokError.code != "" {
|
||||
return nil, refreshToken, tokError
|
||||
}
|
||||
if token.Value == "" {
|
||||
return nil, refreshToken, errors.New("auth: server response missing access_token")
|
||||
}
|
||||
return token, refreshToken, nil
|
||||
}
|
||||
66
src/server/vendor/cloud.google.com/go/compute/metadata/CHANGES.md
generated
vendored
Normal file
66
src/server/vendor/cloud.google.com/go/compute/metadata/CHANGES.md
generated
vendored
Normal file
@ -0,0 +1,66 @@
|
||||
# Changes
|
||||
|
||||
## [0.6.0](https://github.com/googleapis/google-cloud-go/compare/compute/metadata/v0.5.2...compute/metadata/v0.6.0) (2024-12-13)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **compute/metadata:** Add debug logging ([#11078](https://github.com/googleapis/google-cloud-go/issues/11078)) ([a816814](https://github.com/googleapis/google-cloud-go/commit/a81681463906e4473570a2f426eb0dc2de64e53f))
|
||||
|
||||
## [0.5.2](https://github.com/googleapis/google-cloud-go/compare/compute/metadata/v0.5.1...compute/metadata/v0.5.2) (2024-09-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compute/metadata:** Close Response Body for failed request ([#10891](https://github.com/googleapis/google-cloud-go/issues/10891)) ([e91d45e](https://github.com/googleapis/google-cloud-go/commit/e91d45e4757a9e354114509ba9800085d9e0ff1f))
|
||||
|
||||
## [0.5.1](https://github.com/googleapis/google-cloud-go/compare/compute/metadata/v0.5.0...compute/metadata/v0.5.1) (2024-09-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compute/metadata:** Check error chain for retryable error ([#10840](https://github.com/googleapis/google-cloud-go/issues/10840)) ([2bdedef](https://github.com/googleapis/google-cloud-go/commit/2bdedeff621b223d63cebc4355fcf83bc68412cd))
|
||||
|
||||
## [0.5.0](https://github.com/googleapis/google-cloud-go/compare/compute/metadata/v0.4.0...compute/metadata/v0.5.0) (2024-07-10)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **compute/metadata:** Add sys check for windows OnGCE ([#10521](https://github.com/googleapis/google-cloud-go/issues/10521)) ([3b9a830](https://github.com/googleapis/google-cloud-go/commit/3b9a83063960d2a2ac20beb47cc15818a68bd302))
|
||||
|
||||
## [0.4.0](https://github.com/googleapis/google-cloud-go/compare/compute/metadata/v0.3.0...compute/metadata/v0.4.0) (2024-07-01)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **compute/metadata:** Add context for all functions/methods ([#10370](https://github.com/googleapis/google-cloud-go/issues/10370)) ([66b8efe](https://github.com/googleapis/google-cloud-go/commit/66b8efe7ad877e052b2987bb4475477e38c67bb3))
|
||||
|
||||
|
||||
### Documentation
|
||||
|
||||
* **compute/metadata:** Update OnGCE description ([#10408](https://github.com/googleapis/google-cloud-go/issues/10408)) ([6a46dca](https://github.com/googleapis/google-cloud-go/commit/6a46dca4eae4f88ec6f88822e01e5bf8aeca787f))
|
||||
|
||||
## [0.3.0](https://github.com/googleapis/google-cloud-go/compare/compute/metadata/v0.2.3...compute/metadata/v0.3.0) (2024-04-15)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **compute/metadata:** Add context aware functions ([#9733](https://github.com/googleapis/google-cloud-go/issues/9733)) ([e4eb5b4](https://github.com/googleapis/google-cloud-go/commit/e4eb5b46ee2aec9d2fc18300bfd66015e25a0510))
|
||||
|
||||
## [0.2.3](https://github.com/googleapis/google-cloud-go/compare/compute/metadata/v0.2.2...compute/metadata/v0.2.3) (2022-12-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compute/metadata:** Switch DNS lookup to an absolute lookup ([119b410](https://github.com/googleapis/google-cloud-go/commit/119b41060c7895e45e48aee5621ad35607c4d021)), refs [#7165](https://github.com/googleapis/google-cloud-go/issues/7165)
|
||||
|
||||
## [0.2.2](https://github.com/googleapis/google-cloud-go/compare/compute/metadata/v0.2.1...compute/metadata/v0.2.2) (2022-12-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compute/metadata:** Set IdleConnTimeout for http.Client ([#7084](https://github.com/googleapis/google-cloud-go/issues/7084)) ([766516a](https://github.com/googleapis/google-cloud-go/commit/766516aaf3816bfb3159efeea65aa3d1d205a3e2)), refs [#5430](https://github.com/googleapis/google-cloud-go/issues/5430)
|
||||
|
||||
## [0.1.0] (2022-10-26)
|
||||
|
||||
Initial release of metadata being it's own module.
|
||||
202
src/server/vendor/cloud.google.com/go/compute/metadata/LICENSE
generated
vendored
Normal file
202
src/server/vendor/cloud.google.com/go/compute/metadata/LICENSE
generated
vendored
Normal file
@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
27
src/server/vendor/cloud.google.com/go/compute/metadata/README.md
generated
vendored
Normal file
27
src/server/vendor/cloud.google.com/go/compute/metadata/README.md
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
# Compute API
|
||||
|
||||
[](https://pkg.go.dev/cloud.google.com/go/compute/metadata)
|
||||
|
||||
This is a utility library for communicating with Google Cloud metadata service
|
||||
on Google Cloud.
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
go get cloud.google.com/go/compute/metadata
|
||||
```
|
||||
|
||||
## Go Version Support
|
||||
|
||||
See the [Go Versions Supported](https://github.com/googleapis/google-cloud-go#go-versions-supported)
|
||||
section in the root directory's README.
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome. Please, see the [CONTRIBUTING](https://github.com/GoogleCloudPlatform/google-cloud-go/blob/main/CONTRIBUTING.md)
|
||||
document for details.
|
||||
|
||||
Please note that this project is released with a Contributor Code of Conduct.
|
||||
By participating in this project you agree to abide by its terms. See
|
||||
[Contributor Code of Conduct](https://github.com/GoogleCloudPlatform/google-cloud-go/blob/main/CONTRIBUTING.md#contributor-code-of-conduct)
|
||||
for more information.
|
||||
149
src/server/vendor/cloud.google.com/go/compute/metadata/log.go
generated
vendored
Normal file
149
src/server/vendor/cloud.google.com/go/compute/metadata/log.go
generated
vendored
Normal file
@ -0,0 +1,149 @@
|
||||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package metadata
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Code below this point is copied from github.com/googleapis/gax-go/v2/internallog
|
||||
// to avoid the dependency. The compute/metadata module is used by too many
|
||||
// non-client library modules that can't justify the dependency.
|
||||
|
||||
// The handler returned if logging is not enabled.
|
||||
type noOpHandler struct{}
|
||||
|
||||
func (h noOpHandler) Enabled(_ context.Context, _ slog.Level) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (h noOpHandler) Handle(_ context.Context, _ slog.Record) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h noOpHandler) WithAttrs(_ []slog.Attr) slog.Handler {
|
||||
return h
|
||||
}
|
||||
|
||||
func (h noOpHandler) WithGroup(_ string) slog.Handler {
|
||||
return h
|
||||
}
|
||||
|
||||
// httpRequest returns a lazily evaluated [slog.LogValuer] for a
|
||||
// [http.Request] and the associated body.
|
||||
func httpRequest(req *http.Request, body []byte) slog.LogValuer {
|
||||
return &request{
|
||||
req: req,
|
||||
payload: body,
|
||||
}
|
||||
}
|
||||
|
||||
type request struct {
|
||||
req *http.Request
|
||||
payload []byte
|
||||
}
|
||||
|
||||
func (r *request) LogValue() slog.Value {
|
||||
if r == nil || r.req == nil {
|
||||
return slog.Value{}
|
||||
}
|
||||
var groupValueAttrs []slog.Attr
|
||||
groupValueAttrs = append(groupValueAttrs, slog.String("method", r.req.Method))
|
||||
groupValueAttrs = append(groupValueAttrs, slog.String("url", r.req.URL.String()))
|
||||
|
||||
var headerAttr []slog.Attr
|
||||
for k, val := range r.req.Header {
|
||||
headerAttr = append(headerAttr, slog.String(k, strings.Join(val, ",")))
|
||||
}
|
||||
if len(headerAttr) > 0 {
|
||||
groupValueAttrs = append(groupValueAttrs, slog.Any("headers", headerAttr))
|
||||
}
|
||||
|
||||
if len(r.payload) > 0 {
|
||||
if attr, ok := processPayload(r.payload); ok {
|
||||
groupValueAttrs = append(groupValueAttrs, attr)
|
||||
}
|
||||
}
|
||||
return slog.GroupValue(groupValueAttrs...)
|
||||
}
|
||||
|
||||
// httpResponse returns a lazily evaluated [slog.LogValuer] for a
|
||||
// [http.Response] and the associated body.
|
||||
func httpResponse(resp *http.Response, body []byte) slog.LogValuer {
|
||||
return &response{
|
||||
resp: resp,
|
||||
payload: body,
|
||||
}
|
||||
}
|
||||
|
||||
type response struct {
|
||||
resp *http.Response
|
||||
payload []byte
|
||||
}
|
||||
|
||||
func (r *response) LogValue() slog.Value {
|
||||
if r == nil {
|
||||
return slog.Value{}
|
||||
}
|
||||
var groupValueAttrs []slog.Attr
|
||||
groupValueAttrs = append(groupValueAttrs, slog.String("status", fmt.Sprint(r.resp.StatusCode)))
|
||||
|
||||
var headerAttr []slog.Attr
|
||||
for k, val := range r.resp.Header {
|
||||
headerAttr = append(headerAttr, slog.String(k, strings.Join(val, ",")))
|
||||
}
|
||||
if len(headerAttr) > 0 {
|
||||
groupValueAttrs = append(groupValueAttrs, slog.Any("headers", headerAttr))
|
||||
}
|
||||
|
||||
if len(r.payload) > 0 {
|
||||
if attr, ok := processPayload(r.payload); ok {
|
||||
groupValueAttrs = append(groupValueAttrs, attr)
|
||||
}
|
||||
}
|
||||
return slog.GroupValue(groupValueAttrs...)
|
||||
}
|
||||
|
||||
func processPayload(payload []byte) (slog.Attr, bool) {
|
||||
peekChar := payload[0]
|
||||
if peekChar == '{' {
|
||||
// JSON object
|
||||
var m map[string]any
|
||||
if err := json.Unmarshal(payload, &m); err == nil {
|
||||
return slog.Any("payload", m), true
|
||||
}
|
||||
} else if peekChar == '[' {
|
||||
// JSON array
|
||||
var m []any
|
||||
if err := json.Unmarshal(payload, &m); err == nil {
|
||||
return slog.Any("payload", m), true
|
||||
}
|
||||
} else {
|
||||
// Everything else
|
||||
buf := &bytes.Buffer{}
|
||||
if err := json.Compact(buf, payload); err != nil {
|
||||
// Write raw payload incase of error
|
||||
buf.Write(payload)
|
||||
}
|
||||
return slog.String("payload", buf.String()), true
|
||||
}
|
||||
return slog.Attr{}, false
|
||||
}
|
||||
872
src/server/vendor/cloud.google.com/go/compute/metadata/metadata.go
generated
vendored
Normal file
872
src/server/vendor/cloud.google.com/go/compute/metadata/metadata.go
generated
vendored
Normal file
@ -0,0 +1,872 @@
|
||||
// Copyright 2014 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package metadata provides access to Google Compute Engine (GCE)
|
||||
// metadata and API service accounts.
|
||||
//
|
||||
// This package is a wrapper around the GCE metadata service,
|
||||
// as documented at https://cloud.google.com/compute/docs/metadata/overview.
|
||||
package metadata // import "cloud.google.com/go/compute/metadata"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// metadataIP is the documented metadata server IP address.
|
||||
metadataIP = "169.254.169.254"
|
||||
|
||||
// metadataHostEnv is the environment variable specifying the
|
||||
// GCE metadata hostname. If empty, the default value of
|
||||
// metadataIP ("169.254.169.254") is used instead.
|
||||
// This is variable name is not defined by any spec, as far as
|
||||
// I know; it was made up for the Go package.
|
||||
metadataHostEnv = "GCE_METADATA_HOST"
|
||||
|
||||
userAgent = "gcloud-golang/0.1"
|
||||
)
|
||||
|
||||
type cachedValue struct {
|
||||
k string
|
||||
trim bool
|
||||
mu sync.Mutex
|
||||
v string
|
||||
}
|
||||
|
||||
var (
|
||||
projID = &cachedValue{k: "project/project-id", trim: true}
|
||||
projNum = &cachedValue{k: "project/numeric-project-id", trim: true}
|
||||
instID = &cachedValue{k: "instance/id", trim: true}
|
||||
)
|
||||
|
||||
var defaultClient = &Client{
|
||||
hc: newDefaultHTTPClient(),
|
||||
logger: slog.New(noOpHandler{}),
|
||||
}
|
||||
|
||||
func newDefaultHTTPClient() *http.Client {
|
||||
return &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Dial: (&net.Dialer{
|
||||
Timeout: 2 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).Dial,
|
||||
IdleConnTimeout: 60 * time.Second,
|
||||
},
|
||||
Timeout: 5 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
// NotDefinedError is returned when requested metadata is not defined.
|
||||
//
|
||||
// The underlying string is the suffix after "/computeMetadata/v1/".
|
||||
//
|
||||
// This error is not returned if the value is defined to be the empty
|
||||
// string.
|
||||
type NotDefinedError string
|
||||
|
||||
func (suffix NotDefinedError) Error() string {
|
||||
return fmt.Sprintf("metadata: GCE metadata %q not defined", string(suffix))
|
||||
}
|
||||
|
||||
func (c *cachedValue) get(ctx context.Context, cl *Client) (v string, err error) {
|
||||
defer c.mu.Unlock()
|
||||
c.mu.Lock()
|
||||
if c.v != "" {
|
||||
return c.v, nil
|
||||
}
|
||||
if c.trim {
|
||||
v, err = cl.getTrimmed(ctx, c.k)
|
||||
} else {
|
||||
v, err = cl.GetWithContext(ctx, c.k)
|
||||
}
|
||||
if err == nil {
|
||||
c.v = v
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
onGCEOnce sync.Once
|
||||
onGCE bool
|
||||
)
|
||||
|
||||
// OnGCE reports whether this process is running on Google Compute Platforms.
|
||||
// NOTE: True returned from `OnGCE` does not guarantee that the metadata server
|
||||
// is accessible from this process and have all the metadata defined.
|
||||
func OnGCE() bool {
|
||||
onGCEOnce.Do(initOnGCE)
|
||||
return onGCE
|
||||
}
|
||||
|
||||
func initOnGCE() {
|
||||
onGCE = testOnGCE()
|
||||
}
|
||||
|
||||
func testOnGCE() bool {
|
||||
// The user explicitly said they're on GCE, so trust them.
|
||||
if os.Getenv(metadataHostEnv) != "" {
|
||||
return true
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
resc := make(chan bool, 2)
|
||||
|
||||
// Try two strategies in parallel.
|
||||
// See https://github.com/googleapis/google-cloud-go/issues/194
|
||||
go func() {
|
||||
req, _ := http.NewRequest("GET", "http://"+metadataIP, nil)
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
res, err := newDefaultHTTPClient().Do(req.WithContext(ctx))
|
||||
if err != nil {
|
||||
resc <- false
|
||||
return
|
||||
}
|
||||
defer res.Body.Close()
|
||||
resc <- res.Header.Get("Metadata-Flavor") == "Google"
|
||||
}()
|
||||
|
||||
go func() {
|
||||
resolver := &net.Resolver{}
|
||||
addrs, err := resolver.LookupHost(ctx, "metadata.google.internal.")
|
||||
if err != nil || len(addrs) == 0 {
|
||||
resc <- false
|
||||
return
|
||||
}
|
||||
resc <- strsContains(addrs, metadataIP)
|
||||
}()
|
||||
|
||||
tryHarder := systemInfoSuggestsGCE()
|
||||
if tryHarder {
|
||||
res := <-resc
|
||||
if res {
|
||||
// The first strategy succeeded, so let's use it.
|
||||
return true
|
||||
}
|
||||
// Wait for either the DNS or metadata server probe to
|
||||
// contradict the other one and say we are running on
|
||||
// GCE. Give it a lot of time to do so, since the system
|
||||
// info already suggests we're running on a GCE BIOS.
|
||||
timer := time.NewTimer(5 * time.Second)
|
||||
defer timer.Stop()
|
||||
select {
|
||||
case res = <-resc:
|
||||
return res
|
||||
case <-timer.C:
|
||||
// Too slow. Who knows what this system is.
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// There's no hint from the system info that we're running on
|
||||
// GCE, so use the first probe's result as truth, whether it's
|
||||
// true or false. The goal here is to optimize for speed for
|
||||
// users who are NOT running on GCE. We can't assume that
|
||||
// either a DNS lookup or an HTTP request to a blackholed IP
|
||||
// address is fast. Worst case this should return when the
|
||||
// metaClient's Transport.ResponseHeaderTimeout or
|
||||
// Transport.Dial.Timeout fires (in two seconds).
|
||||
return <-resc
|
||||
}
|
||||
|
||||
// Subscribe calls Client.SubscribeWithContext on the default client.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [SubscribeWithContext].
|
||||
func Subscribe(suffix string, fn func(v string, ok bool) error) error {
|
||||
return defaultClient.SubscribeWithContext(context.Background(), suffix, func(ctx context.Context, v string, ok bool) error { return fn(v, ok) })
|
||||
}
|
||||
|
||||
// SubscribeWithContext calls Client.SubscribeWithContext on the default client.
|
||||
func SubscribeWithContext(ctx context.Context, suffix string, fn func(ctx context.Context, v string, ok bool) error) error {
|
||||
return defaultClient.SubscribeWithContext(ctx, suffix, fn)
|
||||
}
|
||||
|
||||
// Get calls Client.GetWithContext on the default client.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [GetWithContext].
|
||||
func Get(suffix string) (string, error) {
|
||||
return defaultClient.GetWithContext(context.Background(), suffix)
|
||||
}
|
||||
|
||||
// GetWithContext calls Client.GetWithContext on the default client.
|
||||
func GetWithContext(ctx context.Context, suffix string) (string, error) {
|
||||
return defaultClient.GetWithContext(ctx, suffix)
|
||||
}
|
||||
|
||||
// ProjectID returns the current instance's project ID string.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [ProjectIDWithContext].
|
||||
func ProjectID() (string, error) {
|
||||
return defaultClient.ProjectIDWithContext(context.Background())
|
||||
}
|
||||
|
||||
// ProjectIDWithContext returns the current instance's project ID string.
|
||||
func ProjectIDWithContext(ctx context.Context) (string, error) {
|
||||
return defaultClient.ProjectIDWithContext(ctx)
|
||||
}
|
||||
|
||||
// NumericProjectID returns the current instance's numeric project ID.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [NumericProjectIDWithContext].
|
||||
func NumericProjectID() (string, error) {
|
||||
return defaultClient.NumericProjectIDWithContext(context.Background())
|
||||
}
|
||||
|
||||
// NumericProjectIDWithContext returns the current instance's numeric project ID.
|
||||
func NumericProjectIDWithContext(ctx context.Context) (string, error) {
|
||||
return defaultClient.NumericProjectIDWithContext(ctx)
|
||||
}
|
||||
|
||||
// InternalIP returns the instance's primary internal IP address.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [InternalIPWithContext].
|
||||
func InternalIP() (string, error) {
|
||||
return defaultClient.InternalIPWithContext(context.Background())
|
||||
}
|
||||
|
||||
// InternalIPWithContext returns the instance's primary internal IP address.
|
||||
func InternalIPWithContext(ctx context.Context) (string, error) {
|
||||
return defaultClient.InternalIPWithContext(ctx)
|
||||
}
|
||||
|
||||
// ExternalIP returns the instance's primary external (public) IP address.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [ExternalIPWithContext].
|
||||
func ExternalIP() (string, error) {
|
||||
return defaultClient.ExternalIPWithContext(context.Background())
|
||||
}
|
||||
|
||||
// ExternalIPWithContext returns the instance's primary external (public) IP address.
|
||||
func ExternalIPWithContext(ctx context.Context) (string, error) {
|
||||
return defaultClient.ExternalIPWithContext(ctx)
|
||||
}
|
||||
|
||||
// Email calls Client.EmailWithContext on the default client.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [EmailWithContext].
|
||||
func Email(serviceAccount string) (string, error) {
|
||||
return defaultClient.EmailWithContext(context.Background(), serviceAccount)
|
||||
}
|
||||
|
||||
// EmailWithContext calls Client.EmailWithContext on the default client.
|
||||
func EmailWithContext(ctx context.Context, serviceAccount string) (string, error) {
|
||||
return defaultClient.EmailWithContext(ctx, serviceAccount)
|
||||
}
|
||||
|
||||
// Hostname returns the instance's hostname. This will be of the form
|
||||
// "<instanceID>.c.<projID>.internal".
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [HostnameWithContext].
|
||||
func Hostname() (string, error) {
|
||||
return defaultClient.HostnameWithContext(context.Background())
|
||||
}
|
||||
|
||||
// HostnameWithContext returns the instance's hostname. This will be of the form
|
||||
// "<instanceID>.c.<projID>.internal".
|
||||
func HostnameWithContext(ctx context.Context) (string, error) {
|
||||
return defaultClient.HostnameWithContext(ctx)
|
||||
}
|
||||
|
||||
// InstanceTags returns the list of user-defined instance tags,
|
||||
// assigned when initially creating a GCE instance.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [InstanceTagsWithContext].
|
||||
func InstanceTags() ([]string, error) {
|
||||
return defaultClient.InstanceTagsWithContext(context.Background())
|
||||
}
|
||||
|
||||
// InstanceTagsWithContext returns the list of user-defined instance tags,
|
||||
// assigned when initially creating a GCE instance.
|
||||
func InstanceTagsWithContext(ctx context.Context) ([]string, error) {
|
||||
return defaultClient.InstanceTagsWithContext(ctx)
|
||||
}
|
||||
|
||||
// InstanceID returns the current VM's numeric instance ID.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [InstanceIDWithContext].
|
||||
func InstanceID() (string, error) {
|
||||
return defaultClient.InstanceIDWithContext(context.Background())
|
||||
}
|
||||
|
||||
// InstanceIDWithContext returns the current VM's numeric instance ID.
|
||||
func InstanceIDWithContext(ctx context.Context) (string, error) {
|
||||
return defaultClient.InstanceIDWithContext(ctx)
|
||||
}
|
||||
|
||||
// InstanceName returns the current VM's instance ID string.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [InstanceNameWithContext].
|
||||
func InstanceName() (string, error) {
|
||||
return defaultClient.InstanceNameWithContext(context.Background())
|
||||
}
|
||||
|
||||
// InstanceNameWithContext returns the current VM's instance ID string.
|
||||
func InstanceNameWithContext(ctx context.Context) (string, error) {
|
||||
return defaultClient.InstanceNameWithContext(ctx)
|
||||
}
|
||||
|
||||
// Zone returns the current VM's zone, such as "us-central1-b".
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [ZoneWithContext].
|
||||
func Zone() (string, error) {
|
||||
return defaultClient.ZoneWithContext(context.Background())
|
||||
}
|
||||
|
||||
// ZoneWithContext returns the current VM's zone, such as "us-central1-b".
|
||||
func ZoneWithContext(ctx context.Context) (string, error) {
|
||||
return defaultClient.ZoneWithContext(ctx)
|
||||
}
|
||||
|
||||
// InstanceAttributes calls Client.InstanceAttributesWithContext on the default client.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [InstanceAttributesWithContext.
|
||||
func InstanceAttributes() ([]string, error) {
|
||||
return defaultClient.InstanceAttributesWithContext(context.Background())
|
||||
}
|
||||
|
||||
// InstanceAttributesWithContext calls Client.ProjectAttributesWithContext on the default client.
|
||||
func InstanceAttributesWithContext(ctx context.Context) ([]string, error) {
|
||||
return defaultClient.InstanceAttributesWithContext(ctx)
|
||||
}
|
||||
|
||||
// ProjectAttributes calls Client.ProjectAttributesWithContext on the default client.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [ProjectAttributesWithContext].
|
||||
func ProjectAttributes() ([]string, error) {
|
||||
return defaultClient.ProjectAttributesWithContext(context.Background())
|
||||
}
|
||||
|
||||
// ProjectAttributesWithContext calls Client.ProjectAttributesWithContext on the default client.
|
||||
func ProjectAttributesWithContext(ctx context.Context) ([]string, error) {
|
||||
return defaultClient.ProjectAttributesWithContext(ctx)
|
||||
}
|
||||
|
||||
// InstanceAttributeValue calls Client.InstanceAttributeValueWithContext on the default client.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [InstanceAttributeValueWithContext].
|
||||
func InstanceAttributeValue(attr string) (string, error) {
|
||||
return defaultClient.InstanceAttributeValueWithContext(context.Background(), attr)
|
||||
}
|
||||
|
||||
// InstanceAttributeValueWithContext calls Client.InstanceAttributeValueWithContext on the default client.
|
||||
func InstanceAttributeValueWithContext(ctx context.Context, attr string) (string, error) {
|
||||
return defaultClient.InstanceAttributeValueWithContext(ctx, attr)
|
||||
}
|
||||
|
||||
// ProjectAttributeValue calls Client.ProjectAttributeValueWithContext on the default client.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [ProjectAttributeValueWithContext].
|
||||
func ProjectAttributeValue(attr string) (string, error) {
|
||||
return defaultClient.ProjectAttributeValueWithContext(context.Background(), attr)
|
||||
}
|
||||
|
||||
// ProjectAttributeValueWithContext calls Client.ProjectAttributeValueWithContext on the default client.
|
||||
func ProjectAttributeValueWithContext(ctx context.Context, attr string) (string, error) {
|
||||
return defaultClient.ProjectAttributeValueWithContext(ctx, attr)
|
||||
}
|
||||
|
||||
// Scopes calls Client.ScopesWithContext on the default client.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [ScopesWithContext].
|
||||
func Scopes(serviceAccount string) ([]string, error) {
|
||||
return defaultClient.ScopesWithContext(context.Background(), serviceAccount)
|
||||
}
|
||||
|
||||
// ScopesWithContext calls Client.ScopesWithContext on the default client.
|
||||
func ScopesWithContext(ctx context.Context, serviceAccount string) ([]string, error) {
|
||||
return defaultClient.ScopesWithContext(ctx, serviceAccount)
|
||||
}
|
||||
|
||||
func strsContains(ss []string, s string) bool {
|
||||
for _, v := range ss {
|
||||
if v == s {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// A Client provides metadata.
|
||||
type Client struct {
|
||||
hc *http.Client
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
// Options for configuring a [Client].
|
||||
type Options struct {
|
||||
// Client is the HTTP client used to make requests. Optional.
|
||||
Client *http.Client
|
||||
// Logger is used to log information about HTTP request and responses.
|
||||
// If not provided, nothing will be logged. Optional.
|
||||
Logger *slog.Logger
|
||||
}
|
||||
|
||||
// NewClient returns a Client that can be used to fetch metadata.
|
||||
// Returns the client that uses the specified http.Client for HTTP requests.
|
||||
// If nil is specified, returns the default client.
|
||||
func NewClient(c *http.Client) *Client {
|
||||
return NewWithOptions(&Options{
|
||||
Client: c,
|
||||
})
|
||||
}
|
||||
|
||||
// NewWithOptions returns a Client that is configured with the provided Options.
|
||||
func NewWithOptions(opts *Options) *Client {
|
||||
if opts == nil {
|
||||
return defaultClient
|
||||
}
|
||||
client := opts.Client
|
||||
if client == nil {
|
||||
client = newDefaultHTTPClient()
|
||||
}
|
||||
logger := opts.Logger
|
||||
if logger == nil {
|
||||
logger = slog.New(noOpHandler{})
|
||||
}
|
||||
return &Client{hc: client, logger: logger}
|
||||
}
|
||||
|
||||
// getETag returns a value from the metadata service as well as the associated ETag.
|
||||
// This func is otherwise equivalent to Get.
|
||||
func (c *Client) getETag(ctx context.Context, suffix string) (value, etag string, err error) {
|
||||
// Using a fixed IP makes it very difficult to spoof the metadata service in
|
||||
// a container, which is an important use-case for local testing of cloud
|
||||
// deployments. To enable spoofing of the metadata service, the environment
|
||||
// variable GCE_METADATA_HOST is first inspected to decide where metadata
|
||||
// requests shall go.
|
||||
host := os.Getenv(metadataHostEnv)
|
||||
if host == "" {
|
||||
// Using 169.254.169.254 instead of "metadata" here because Go
|
||||
// binaries built with the "netgo" tag and without cgo won't
|
||||
// know the search suffix for "metadata" is
|
||||
// ".google.internal", and this IP address is documented as
|
||||
// being stable anyway.
|
||||
host = metadataIP
|
||||
}
|
||||
suffix = strings.TrimLeft(suffix, "/")
|
||||
u := "http://" + host + "/computeMetadata/v1/" + suffix
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", u, nil)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
req.Header.Set("Metadata-Flavor", "Google")
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
var res *http.Response
|
||||
var reqErr error
|
||||
var body []byte
|
||||
retryer := newRetryer()
|
||||
for {
|
||||
c.logger.DebugContext(ctx, "metadata request", "request", httpRequest(req, nil))
|
||||
res, reqErr = c.hc.Do(req)
|
||||
var code int
|
||||
if res != nil {
|
||||
code = res.StatusCode
|
||||
body, err = io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
res.Body.Close()
|
||||
return "", "", err
|
||||
}
|
||||
c.logger.DebugContext(ctx, "metadata response", "response", httpResponse(res, body))
|
||||
res.Body.Close()
|
||||
}
|
||||
if delay, shouldRetry := retryer.Retry(code, reqErr); shouldRetry {
|
||||
if res != nil && res.Body != nil {
|
||||
res.Body.Close()
|
||||
}
|
||||
if err := sleep(ctx, delay); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
if reqErr != nil {
|
||||
return "", "", reqErr
|
||||
}
|
||||
if res.StatusCode == http.StatusNotFound {
|
||||
return "", "", NotDefinedError(suffix)
|
||||
}
|
||||
if res.StatusCode != 200 {
|
||||
return "", "", &Error{Code: res.StatusCode, Message: string(body)}
|
||||
}
|
||||
return string(body), res.Header.Get("Etag"), nil
|
||||
}
|
||||
|
||||
// Get returns a value from the metadata service.
|
||||
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
|
||||
//
|
||||
// If the GCE_METADATA_HOST environment variable is not defined, a default of
|
||||
// 169.254.169.254 will be used instead.
|
||||
//
|
||||
// If the requested metadata is not defined, the returned error will
|
||||
// be of type NotDefinedError.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [Client.GetWithContext].
|
||||
func (c *Client) Get(suffix string) (string, error) {
|
||||
return c.GetWithContext(context.Background(), suffix)
|
||||
}
|
||||
|
||||
// GetWithContext returns a value from the metadata service.
|
||||
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
|
||||
//
|
||||
// If the GCE_METADATA_HOST environment variable is not defined, a default of
|
||||
// 169.254.169.254 will be used instead.
|
||||
//
|
||||
// If the requested metadata is not defined, the returned error will
|
||||
// be of type NotDefinedError.
|
||||
//
|
||||
// NOTE: Without an extra deadline in the context this call can take in the
|
||||
// worst case, with internal backoff retries, up to 15 seconds (e.g. when server
|
||||
// is responding slowly). Pass context with additional timeouts when needed.
|
||||
func (c *Client) GetWithContext(ctx context.Context, suffix string) (string, error) {
|
||||
val, _, err := c.getETag(ctx, suffix)
|
||||
return val, err
|
||||
}
|
||||
|
||||
func (c *Client) getTrimmed(ctx context.Context, suffix string) (s string, err error) {
|
||||
s, err = c.GetWithContext(ctx, suffix)
|
||||
s = strings.TrimSpace(s)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Client) lines(ctx context.Context, suffix string) ([]string, error) {
|
||||
j, err := c.GetWithContext(ctx, suffix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s := strings.Split(strings.TrimSpace(j), "\n")
|
||||
for i := range s {
|
||||
s[i] = strings.TrimSpace(s[i])
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// ProjectID returns the current instance's project ID string.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [Client.ProjectIDWithContext].
|
||||
func (c *Client) ProjectID() (string, error) { return c.ProjectIDWithContext(context.Background()) }
|
||||
|
||||
// ProjectIDWithContext returns the current instance's project ID string.
|
||||
func (c *Client) ProjectIDWithContext(ctx context.Context) (string, error) { return projID.get(ctx, c) }
|
||||
|
||||
// NumericProjectID returns the current instance's numeric project ID.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [Client.NumericProjectIDWithContext].
|
||||
func (c *Client) NumericProjectID() (string, error) {
|
||||
return c.NumericProjectIDWithContext(context.Background())
|
||||
}
|
||||
|
||||
// NumericProjectIDWithContext returns the current instance's numeric project ID.
|
||||
func (c *Client) NumericProjectIDWithContext(ctx context.Context) (string, error) {
|
||||
return projNum.get(ctx, c)
|
||||
}
|
||||
|
||||
// InstanceID returns the current VM's numeric instance ID.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [Client.InstanceIDWithContext].
|
||||
func (c *Client) InstanceID() (string, error) {
|
||||
return c.InstanceIDWithContext(context.Background())
|
||||
}
|
||||
|
||||
// InstanceIDWithContext returns the current VM's numeric instance ID.
|
||||
func (c *Client) InstanceIDWithContext(ctx context.Context) (string, error) {
|
||||
return instID.get(ctx, c)
|
||||
}
|
||||
|
||||
// InternalIP returns the instance's primary internal IP address.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [Client.InternalIPWithContext].
|
||||
func (c *Client) InternalIP() (string, error) {
|
||||
return c.InternalIPWithContext(context.Background())
|
||||
}
|
||||
|
||||
// InternalIPWithContext returns the instance's primary internal IP address.
|
||||
func (c *Client) InternalIPWithContext(ctx context.Context) (string, error) {
|
||||
return c.getTrimmed(ctx, "instance/network-interfaces/0/ip")
|
||||
}
|
||||
|
||||
// Email returns the email address associated with the service account.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [Client.EmailWithContext].
|
||||
func (c *Client) Email(serviceAccount string) (string, error) {
|
||||
return c.EmailWithContext(context.Background(), serviceAccount)
|
||||
}
|
||||
|
||||
// EmailWithContext returns the email address associated with the service account.
|
||||
// The serviceAccount parameter default value (empty string or "default" value)
|
||||
// will use the instance's main account.
|
||||
func (c *Client) EmailWithContext(ctx context.Context, serviceAccount string) (string, error) {
|
||||
if serviceAccount == "" {
|
||||
serviceAccount = "default"
|
||||
}
|
||||
return c.getTrimmed(ctx, "instance/service-accounts/"+serviceAccount+"/email")
|
||||
}
|
||||
|
||||
// ExternalIP returns the instance's primary external (public) IP address.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [Client.ExternalIPWithContext].
|
||||
func (c *Client) ExternalIP() (string, error) {
|
||||
return c.ExternalIPWithContext(context.Background())
|
||||
}
|
||||
|
||||
// ExternalIPWithContext returns the instance's primary external (public) IP address.
|
||||
func (c *Client) ExternalIPWithContext(ctx context.Context) (string, error) {
|
||||
return c.getTrimmed(ctx, "instance/network-interfaces/0/access-configs/0/external-ip")
|
||||
}
|
||||
|
||||
// Hostname returns the instance's hostname. This will be of the form
|
||||
// "<instanceID>.c.<projID>.internal".
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [Client.HostnameWithContext].
|
||||
func (c *Client) Hostname() (string, error) {
|
||||
return c.HostnameWithContext(context.Background())
|
||||
}
|
||||
|
||||
// HostnameWithContext returns the instance's hostname. This will be of the form
|
||||
// "<instanceID>.c.<projID>.internal".
|
||||
func (c *Client) HostnameWithContext(ctx context.Context) (string, error) {
|
||||
return c.getTrimmed(ctx, "instance/hostname")
|
||||
}
|
||||
|
||||
// InstanceTags returns the list of user-defined instance tags.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [Client.InstanceTagsWithContext].
|
||||
func (c *Client) InstanceTags() ([]string, error) {
|
||||
return c.InstanceTagsWithContext(context.Background())
|
||||
}
|
||||
|
||||
// InstanceTagsWithContext returns the list of user-defined instance tags,
|
||||
// assigned when initially creating a GCE instance.
|
||||
func (c *Client) InstanceTagsWithContext(ctx context.Context) ([]string, error) {
|
||||
var s []string
|
||||
j, err := c.GetWithContext(ctx, "instance/tags")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := json.NewDecoder(strings.NewReader(j)).Decode(&s); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// InstanceName returns the current VM's instance ID string.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [Client.InstanceNameWithContext].
|
||||
func (c *Client) InstanceName() (string, error) {
|
||||
return c.InstanceNameWithContext(context.Background())
|
||||
}
|
||||
|
||||
// InstanceNameWithContext returns the current VM's instance ID string.
|
||||
func (c *Client) InstanceNameWithContext(ctx context.Context) (string, error) {
|
||||
return c.getTrimmed(ctx, "instance/name")
|
||||
}
|
||||
|
||||
// Zone returns the current VM's zone, such as "us-central1-b".
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [Client.ZoneWithContext].
|
||||
func (c *Client) Zone() (string, error) {
|
||||
return c.ZoneWithContext(context.Background())
|
||||
}
|
||||
|
||||
// ZoneWithContext returns the current VM's zone, such as "us-central1-b".
|
||||
func (c *Client) ZoneWithContext(ctx context.Context) (string, error) {
|
||||
zone, err := c.getTrimmed(ctx, "instance/zone")
|
||||
// zone is of the form "projects/<projNum>/zones/<zoneName>".
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return zone[strings.LastIndex(zone, "/")+1:], nil
|
||||
}
|
||||
|
||||
// InstanceAttributes returns the list of user-defined attributes,
|
||||
// assigned when initially creating a GCE VM instance. The value of an
|
||||
// attribute can be obtained with InstanceAttributeValue.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [Client.InstanceAttributesWithContext].
|
||||
func (c *Client) InstanceAttributes() ([]string, error) {
|
||||
return c.InstanceAttributesWithContext(context.Background())
|
||||
}
|
||||
|
||||
// InstanceAttributesWithContext returns the list of user-defined attributes,
|
||||
// assigned when initially creating a GCE VM instance. The value of an
|
||||
// attribute can be obtained with InstanceAttributeValue.
|
||||
func (c *Client) InstanceAttributesWithContext(ctx context.Context) ([]string, error) {
|
||||
return c.lines(ctx, "instance/attributes/")
|
||||
}
|
||||
|
||||
// ProjectAttributes returns the list of user-defined attributes
|
||||
// applying to the project as a whole, not just this VM. The value of
|
||||
// an attribute can be obtained with ProjectAttributeValue.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [Client.ProjectAttributesWithContext].
|
||||
func (c *Client) ProjectAttributes() ([]string, error) {
|
||||
return c.ProjectAttributesWithContext(context.Background())
|
||||
}
|
||||
|
||||
// ProjectAttributesWithContext returns the list of user-defined attributes
|
||||
// applying to the project as a whole, not just this VM. The value of
|
||||
// an attribute can be obtained with ProjectAttributeValue.
|
||||
func (c *Client) ProjectAttributesWithContext(ctx context.Context) ([]string, error) {
|
||||
return c.lines(ctx, "project/attributes/")
|
||||
}
|
||||
|
||||
// InstanceAttributeValue returns the value of the provided VM
|
||||
// instance attribute.
|
||||
//
|
||||
// If the requested attribute is not defined, the returned error will
|
||||
// be of type NotDefinedError.
|
||||
//
|
||||
// InstanceAttributeValue may return ("", nil) if the attribute was
|
||||
// defined to be the empty string.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [Client.InstanceAttributeValueWithContext].
|
||||
func (c *Client) InstanceAttributeValue(attr string) (string, error) {
|
||||
return c.InstanceAttributeValueWithContext(context.Background(), attr)
|
||||
}
|
||||
|
||||
// InstanceAttributeValueWithContext returns the value of the provided VM
|
||||
// instance attribute.
|
||||
//
|
||||
// If the requested attribute is not defined, the returned error will
|
||||
// be of type NotDefinedError.
|
||||
//
|
||||
// InstanceAttributeValue may return ("", nil) if the attribute was
|
||||
// defined to be the empty string.
|
||||
func (c *Client) InstanceAttributeValueWithContext(ctx context.Context, attr string) (string, error) {
|
||||
return c.GetWithContext(ctx, "instance/attributes/"+attr)
|
||||
}
|
||||
|
||||
// ProjectAttributeValue returns the value of the provided
|
||||
// project attribute.
|
||||
//
|
||||
// If the requested attribute is not defined, the returned error will
|
||||
// be of type NotDefinedError.
|
||||
//
|
||||
// ProjectAttributeValue may return ("", nil) if the attribute was
|
||||
// defined to be the empty string.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [Client.ProjectAttributeValueWithContext].
|
||||
func (c *Client) ProjectAttributeValue(attr string) (string, error) {
|
||||
return c.ProjectAttributeValueWithContext(context.Background(), attr)
|
||||
}
|
||||
|
||||
// ProjectAttributeValueWithContext returns the value of the provided
|
||||
// project attribute.
|
||||
//
|
||||
// If the requested attribute is not defined, the returned error will
|
||||
// be of type NotDefinedError.
|
||||
//
|
||||
// ProjectAttributeValue may return ("", nil) if the attribute was
|
||||
// defined to be the empty string.
|
||||
func (c *Client) ProjectAttributeValueWithContext(ctx context.Context, attr string) (string, error) {
|
||||
return c.GetWithContext(ctx, "project/attributes/"+attr)
|
||||
}
|
||||
|
||||
// Scopes returns the service account scopes for the given account.
|
||||
// The account may be empty or the string "default" to use the instance's
|
||||
// main account.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [Client.ScopesWithContext].
|
||||
func (c *Client) Scopes(serviceAccount string) ([]string, error) {
|
||||
return c.ScopesWithContext(context.Background(), serviceAccount)
|
||||
}
|
||||
|
||||
// ScopesWithContext returns the service account scopes for the given account.
|
||||
// The account may be empty or the string "default" to use the instance's
|
||||
// main account.
|
||||
func (c *Client) ScopesWithContext(ctx context.Context, serviceAccount string) ([]string, error) {
|
||||
if serviceAccount == "" {
|
||||
serviceAccount = "default"
|
||||
}
|
||||
return c.lines(ctx, "instance/service-accounts/"+serviceAccount+"/scopes")
|
||||
}
|
||||
|
||||
// Subscribe subscribes to a value from the metadata service.
|
||||
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
|
||||
// The suffix may contain query parameters.
|
||||
//
|
||||
// Deprecated: Please use the context aware variant [Client.SubscribeWithContext].
|
||||
func (c *Client) Subscribe(suffix string, fn func(v string, ok bool) error) error {
|
||||
return c.SubscribeWithContext(context.Background(), suffix, func(ctx context.Context, v string, ok bool) error { return fn(v, ok) })
|
||||
}
|
||||
|
||||
// SubscribeWithContext subscribes to a value from the metadata service.
|
||||
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
|
||||
// The suffix may contain query parameters.
|
||||
//
|
||||
// SubscribeWithContext calls fn with the latest metadata value indicated by the
|
||||
// provided suffix. If the metadata value is deleted, fn is called with the
|
||||
// empty string and ok false. Subscribe blocks until fn returns a non-nil error
|
||||
// or the value is deleted. Subscribe returns the error value returned from the
|
||||
// last call to fn, which may be nil when ok == false.
|
||||
func (c *Client) SubscribeWithContext(ctx context.Context, suffix string, fn func(ctx context.Context, v string, ok bool) error) error {
|
||||
const failedSubscribeSleep = time.Second * 5
|
||||
|
||||
// First check to see if the metadata value exists at all.
|
||||
val, lastETag, err := c.getETag(ctx, suffix)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := fn(ctx, val, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ok := true
|
||||
if strings.ContainsRune(suffix, '?') {
|
||||
suffix += "&wait_for_change=true&last_etag="
|
||||
} else {
|
||||
suffix += "?wait_for_change=true&last_etag="
|
||||
}
|
||||
for {
|
||||
val, etag, err := c.getETag(ctx, suffix+url.QueryEscape(lastETag))
|
||||
if err != nil {
|
||||
if _, deleted := err.(NotDefinedError); !deleted {
|
||||
time.Sleep(failedSubscribeSleep)
|
||||
continue // Retry on other errors.
|
||||
}
|
||||
ok = false
|
||||
}
|
||||
lastETag = etag
|
||||
|
||||
if err := fn(ctx, val, ok); err != nil || !ok {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Error contains an error response from the server.
|
||||
type Error struct {
|
||||
// Code is the HTTP response status code.
|
||||
Code int
|
||||
// Message is the server response message.
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
return fmt.Sprintf("compute: Received %d `%s`", e.Code, e.Message)
|
||||
}
|
||||
114
src/server/vendor/cloud.google.com/go/compute/metadata/retry.go
generated
vendored
Normal file
114
src/server/vendor/cloud.google.com/go/compute/metadata/retry.go
generated
vendored
Normal file
@ -0,0 +1,114 @@
|
||||
// Copyright 2021 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package metadata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
maxRetryAttempts = 5
|
||||
)
|
||||
|
||||
var (
|
||||
syscallRetryable = func(error) bool { return false }
|
||||
)
|
||||
|
||||
// defaultBackoff is basically equivalent to gax.Backoff without the need for
|
||||
// the dependency.
|
||||
type defaultBackoff struct {
|
||||
max time.Duration
|
||||
mul float64
|
||||
cur time.Duration
|
||||
}
|
||||
|
||||
func (b *defaultBackoff) Pause() time.Duration {
|
||||
d := time.Duration(1 + rand.Int63n(int64(b.cur)))
|
||||
b.cur = time.Duration(float64(b.cur) * b.mul)
|
||||
if b.cur > b.max {
|
||||
b.cur = b.max
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// sleep is the equivalent of gax.Sleep without the need for the dependency.
|
||||
func sleep(ctx context.Context, d time.Duration) error {
|
||||
t := time.NewTimer(d)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
t.Stop()
|
||||
return ctx.Err()
|
||||
case <-t.C:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func newRetryer() *metadataRetryer {
|
||||
return &metadataRetryer{bo: &defaultBackoff{
|
||||
cur: 100 * time.Millisecond,
|
||||
max: 30 * time.Second,
|
||||
mul: 2,
|
||||
}}
|
||||
}
|
||||
|
||||
type backoff interface {
|
||||
Pause() time.Duration
|
||||
}
|
||||
|
||||
type metadataRetryer struct {
|
||||
bo backoff
|
||||
attempts int
|
||||
}
|
||||
|
||||
func (r *metadataRetryer) Retry(status int, err error) (time.Duration, bool) {
|
||||
if status == http.StatusOK {
|
||||
return 0, false
|
||||
}
|
||||
retryOk := shouldRetry(status, err)
|
||||
if !retryOk {
|
||||
return 0, false
|
||||
}
|
||||
if r.attempts == maxRetryAttempts {
|
||||
return 0, false
|
||||
}
|
||||
r.attempts++
|
||||
return r.bo.Pause(), true
|
||||
}
|
||||
|
||||
func shouldRetry(status int, err error) bool {
|
||||
if 500 <= status && status <= 599 {
|
||||
return true
|
||||
}
|
||||
if err == io.ErrUnexpectedEOF {
|
||||
return true
|
||||
}
|
||||
// Transient network errors should be retried.
|
||||
if syscallRetryable(err) {
|
||||
return true
|
||||
}
|
||||
if err, ok := err.(interface{ Temporary() bool }); ok {
|
||||
if err.Temporary() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if err, ok := err.(interface{ Unwrap() error }); ok {
|
||||
return shouldRetry(status, err.Unwrap())
|
||||
}
|
||||
return false
|
||||
}
|
||||
31
src/server/vendor/cloud.google.com/go/compute/metadata/retry_linux.go
generated
vendored
Normal file
31
src/server/vendor/cloud.google.com/go/compute/metadata/retry_linux.go
generated
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
// Copyright 2021 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package metadata
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Initialize syscallRetryable to return true on transient socket-level
|
||||
// errors. These errors are specific to Linux.
|
||||
syscallRetryable = func(err error) bool {
|
||||
return errors.Is(err, syscall.ECONNRESET) || errors.Is(err, syscall.ECONNREFUSED)
|
||||
}
|
||||
}
|
||||
26
src/server/vendor/cloud.google.com/go/compute/metadata/syscheck.go
generated
vendored
Normal file
26
src/server/vendor/cloud.google.com/go/compute/metadata/syscheck.go
generated
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build !windows && !linux
|
||||
|
||||
package metadata
|
||||
|
||||
// systemInfoSuggestsGCE reports whether the local system (without
|
||||
// doing network requests) suggests that we're running on GCE. If this
|
||||
// returns true, testOnGCE tries a bit harder to reach its metadata
|
||||
// server.
|
||||
func systemInfoSuggestsGCE() bool {
|
||||
// We don't currently have checks for other GOOS
|
||||
return false
|
||||
}
|
||||
28
src/server/vendor/cloud.google.com/go/compute/metadata/syscheck_linux.go
generated
vendored
Normal file
28
src/server/vendor/cloud.google.com/go/compute/metadata/syscheck_linux.go
generated
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build linux
|
||||
|
||||
package metadata
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func systemInfoSuggestsGCE() bool {
|
||||
b, _ := os.ReadFile("/sys/class/dmi/id/product_name")
|
||||
name := strings.TrimSpace(string(b))
|
||||
return name == "Google" || name == "Google Compute Engine"
|
||||
}
|
||||
38
src/server/vendor/cloud.google.com/go/compute/metadata/syscheck_windows.go
generated
vendored
Normal file
38
src/server/vendor/cloud.google.com/go/compute/metadata/syscheck_windows.go
generated
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build windows
|
||||
|
||||
package metadata
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sys/windows/registry"
|
||||
)
|
||||
|
||||
func systemInfoSuggestsGCE() bool {
|
||||
k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SYSTEM\HardwareConfig\Current`, registry.QUERY_VALUE)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer k.Close()
|
||||
|
||||
s, _, err := k.GetStringValue("SystemProductName")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
s = strings.TrimSpace(s)
|
||||
return strings.HasPrefix(s, "Google")
|
||||
}
|
||||
2
src/server/vendor/github.com/cespare/xxhash/v2/README.md
generated
vendored
2
src/server/vendor/github.com/cespare/xxhash/v2/README.md
generated
vendored
@ -70,3 +70,5 @@ benchstat <(go test -benchtime 500ms -count 15 -bench 'Sum64$')
|
||||
- [VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics)
|
||||
- [FreeCache](https://github.com/coocood/freecache)
|
||||
- [FastCache](https://github.com/VictoriaMetrics/fastcache)
|
||||
- [Ristretto](https://github.com/dgraph-io/ristretto)
|
||||
- [Badger](https://github.com/dgraph-io/badger)
|
||||
|
||||
29
src/server/vendor/github.com/cespare/xxhash/v2/xxhash.go
generated
vendored
29
src/server/vendor/github.com/cespare/xxhash/v2/xxhash.go
generated
vendored
@ -19,10 +19,13 @@ const (
|
||||
// Store the primes in an array as well.
|
||||
//
|
||||
// The consts are used when possible in Go code to avoid MOVs but we need a
|
||||
// contiguous array of the assembly code.
|
||||
// contiguous array for the assembly code.
|
||||
var primes = [...]uint64{prime1, prime2, prime3, prime4, prime5}
|
||||
|
||||
// Digest implements hash.Hash64.
|
||||
//
|
||||
// Note that a zero-valued Digest is not ready to receive writes.
|
||||
// Call Reset or create a Digest using New before calling other methods.
|
||||
type Digest struct {
|
||||
v1 uint64
|
||||
v2 uint64
|
||||
@ -33,19 +36,31 @@ type Digest struct {
|
||||
n int // how much of mem is used
|
||||
}
|
||||
|
||||
// New creates a new Digest that computes the 64-bit xxHash algorithm.
|
||||
// New creates a new Digest with a zero seed.
|
||||
func New() *Digest {
|
||||
return NewWithSeed(0)
|
||||
}
|
||||
|
||||
// NewWithSeed creates a new Digest with the given seed.
|
||||
func NewWithSeed(seed uint64) *Digest {
|
||||
var d Digest
|
||||
d.Reset()
|
||||
d.ResetWithSeed(seed)
|
||||
return &d
|
||||
}
|
||||
|
||||
// Reset clears the Digest's state so that it can be reused.
|
||||
// It uses a seed value of zero.
|
||||
func (d *Digest) Reset() {
|
||||
d.v1 = primes[0] + prime2
|
||||
d.v2 = prime2
|
||||
d.v3 = 0
|
||||
d.v4 = -primes[0]
|
||||
d.ResetWithSeed(0)
|
||||
}
|
||||
|
||||
// ResetWithSeed clears the Digest's state so that it can be reused.
|
||||
// It uses the given seed to initialize the state.
|
||||
func (d *Digest) ResetWithSeed(seed uint64) {
|
||||
d.v1 = seed + prime1 + prime2
|
||||
d.v2 = seed + prime2
|
||||
d.v3 = seed
|
||||
d.v4 = seed - prime1
|
||||
d.total = 0
|
||||
d.n = 0
|
||||
}
|
||||
|
||||
2
src/server/vendor/github.com/cespare/xxhash/v2/xxhash_asm.go
generated
vendored
2
src/server/vendor/github.com/cespare/xxhash/v2/xxhash_asm.go
generated
vendored
@ -6,7 +6,7 @@
|
||||
|
||||
package xxhash
|
||||
|
||||
// Sum64 computes the 64-bit xxHash digest of b.
|
||||
// Sum64 computes the 64-bit xxHash digest of b with a zero seed.
|
||||
//
|
||||
//go:noescape
|
||||
func Sum64(b []byte) uint64
|
||||
|
||||
2
src/server/vendor/github.com/cespare/xxhash/v2/xxhash_other.go
generated
vendored
2
src/server/vendor/github.com/cespare/xxhash/v2/xxhash_other.go
generated
vendored
@ -3,7 +3,7 @@
|
||||
|
||||
package xxhash
|
||||
|
||||
// Sum64 computes the 64-bit xxHash digest of b.
|
||||
// Sum64 computes the 64-bit xxHash digest of b with a zero seed.
|
||||
func Sum64(b []byte) uint64 {
|
||||
// A simpler version would be
|
||||
// d := New()
|
||||
|
||||
2
src/server/vendor/github.com/cespare/xxhash/v2/xxhash_safe.go
generated
vendored
2
src/server/vendor/github.com/cespare/xxhash/v2/xxhash_safe.go
generated
vendored
@ -5,7 +5,7 @@
|
||||
|
||||
package xxhash
|
||||
|
||||
// Sum64String computes the 64-bit xxHash digest of s.
|
||||
// Sum64String computes the 64-bit xxHash digest of s with a zero seed.
|
||||
func Sum64String(s string) uint64 {
|
||||
return Sum64([]byte(s))
|
||||
}
|
||||
|
||||
2
src/server/vendor/github.com/cespare/xxhash/v2/xxhash_unsafe.go
generated
vendored
2
src/server/vendor/github.com/cespare/xxhash/v2/xxhash_unsafe.go
generated
vendored
@ -33,7 +33,7 @@ import (
|
||||
//
|
||||
// See https://github.com/golang/go/issues/42739 for discussion.
|
||||
|
||||
// Sum64String computes the 64-bit xxHash digest of s.
|
||||
// Sum64String computes the 64-bit xxHash digest of s with a zero seed.
|
||||
// It may be faster than Sum64([]byte(s)) by avoiding a copy.
|
||||
func Sum64String(s string) uint64 {
|
||||
b := *(*[]byte)(unsafe.Pointer(&sliceHeader{s, len(s)}))
|
||||
|
||||
0
src/server/vendor/github.com/felixge/httpsnoop/.gitignore
generated
vendored
Normal file
0
src/server/vendor/github.com/felixge/httpsnoop/.gitignore
generated
vendored
Normal file
19
src/server/vendor/github.com/felixge/httpsnoop/LICENSE.txt
generated
vendored
Normal file
19
src/server/vendor/github.com/felixge/httpsnoop/LICENSE.txt
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (c) 2016 Felix Geisendörfer (felix@debuggable.com)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
10
src/server/vendor/github.com/felixge/httpsnoop/Makefile
generated
vendored
Normal file
10
src/server/vendor/github.com/felixge/httpsnoop/Makefile
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
.PHONY: ci generate clean
|
||||
|
||||
ci: clean generate
|
||||
go test -race -v ./...
|
||||
|
||||
generate:
|
||||
go generate .
|
||||
|
||||
clean:
|
||||
rm -rf *_generated*.go
|
||||
95
src/server/vendor/github.com/felixge/httpsnoop/README.md
generated
vendored
Normal file
95
src/server/vendor/github.com/felixge/httpsnoop/README.md
generated
vendored
Normal file
@ -0,0 +1,95 @@
|
||||
# httpsnoop
|
||||
|
||||
Package httpsnoop provides an easy way to capture http related metrics (i.e.
|
||||
response time, bytes written, and http status code) from your application's
|
||||
http.Handlers.
|
||||
|
||||
Doing this requires non-trivial wrapping of the http.ResponseWriter interface,
|
||||
which is also exposed for users interested in a more low-level API.
|
||||
|
||||
[](https://pkg.go.dev/github.com/felixge/httpsnoop)
|
||||
[](https://github.com/felixge/httpsnoop/actions/workflows/main.yaml)
|
||||
|
||||
## Usage Example
|
||||
|
||||
```go
|
||||
// myH is your app's http handler, perhaps a http.ServeMux or similar.
|
||||
var myH http.Handler
|
||||
// wrappedH wraps myH in order to log every request.
|
||||
wrappedH := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
m := httpsnoop.CaptureMetrics(myH, w, r)
|
||||
log.Printf(
|
||||
"%s %s (code=%d dt=%s written=%d)",
|
||||
r.Method,
|
||||
r.URL,
|
||||
m.Code,
|
||||
m.Duration,
|
||||
m.Written,
|
||||
)
|
||||
})
|
||||
http.ListenAndServe(":8080", wrappedH)
|
||||
```
|
||||
|
||||
## Why this package exists
|
||||
|
||||
Instrumenting an application's http.Handler is surprisingly difficult.
|
||||
|
||||
However if you google for e.g. "capture ResponseWriter status code" you'll find
|
||||
lots of advise and code examples that suggest it to be a fairly trivial
|
||||
undertaking. Unfortunately everything I've seen so far has a high chance of
|
||||
breaking your application.
|
||||
|
||||
The main problem is that a `http.ResponseWriter` often implements additional
|
||||
interfaces such as `http.Flusher`, `http.CloseNotifier`, `http.Hijacker`, `http.Pusher`, and
|
||||
`io.ReaderFrom`. So the naive approach of just wrapping `http.ResponseWriter`
|
||||
in your own struct that also implements the `http.ResponseWriter` interface
|
||||
will hide the additional interfaces mentioned above. This has a high change of
|
||||
introducing subtle bugs into any non-trivial application.
|
||||
|
||||
Another approach I've seen people take is to return a struct that implements
|
||||
all of the interfaces above. However, that's also problematic, because it's
|
||||
difficult to fake some of these interfaces behaviors when the underlying
|
||||
`http.ResponseWriter` doesn't have an implementation. It's also dangerous,
|
||||
because an application may choose to operate differently, merely because it
|
||||
detects the presence of these additional interfaces.
|
||||
|
||||
This package solves this problem by checking which additional interfaces a
|
||||
`http.ResponseWriter` implements, returning a wrapped version implementing the
|
||||
exact same set of interfaces.
|
||||
|
||||
Additionally this package properly handles edge cases such as `WriteHeader` not
|
||||
being called, or called more than once, as well as concurrent calls to
|
||||
`http.ResponseWriter` methods, and even calls happening after the wrapped
|
||||
`ServeHTTP` has already returned.
|
||||
|
||||
Unfortunately this package is not perfect either. It's possible that it is
|
||||
still missing some interfaces provided by the go core (let me know if you find
|
||||
one), and it won't work for applications adding their own interfaces into the
|
||||
mix. You can however use `httpsnoop.Unwrap(w)` to access the underlying
|
||||
`http.ResponseWriter` and type-assert the result to its other interfaces.
|
||||
|
||||
However, hopefully the explanation above has sufficiently scared you of rolling
|
||||
your own solution to this problem. httpsnoop may still break your application,
|
||||
but at least it tries to avoid it as much as possible.
|
||||
|
||||
Anyway, the real problem here is that smuggling additional interfaces inside
|
||||
`http.ResponseWriter` is a problematic design choice, but it probably goes as
|
||||
deep as the Go language specification itself. But that's okay, I still prefer
|
||||
Go over the alternatives ;).
|
||||
|
||||
## Performance
|
||||
|
||||
```
|
||||
BenchmarkBaseline-8 20000 94912 ns/op
|
||||
BenchmarkCaptureMetrics-8 20000 95461 ns/op
|
||||
```
|
||||
|
||||
As you can see, using `CaptureMetrics` on a vanilla http.Handler introduces an
|
||||
overhead of ~500 ns per http request on my machine. However, the margin of
|
||||
error appears to be larger than that, therefor it should be reasonable to
|
||||
assume that the overhead introduced by `CaptureMetrics` is absolutely
|
||||
negligible.
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
86
src/server/vendor/github.com/felixge/httpsnoop/capture_metrics.go
generated
vendored
Normal file
86
src/server/vendor/github.com/felixge/httpsnoop/capture_metrics.go
generated
vendored
Normal file
@ -0,0 +1,86 @@
|
||||
package httpsnoop
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Metrics holds metrics captured from CaptureMetrics.
|
||||
type Metrics struct {
|
||||
// Code is the first http response code passed to the WriteHeader func of
|
||||
// the ResponseWriter. If no such call is made, a default code of 200 is
|
||||
// assumed instead.
|
||||
Code int
|
||||
// Duration is the time it took to execute the handler.
|
||||
Duration time.Duration
|
||||
// Written is the number of bytes successfully written by the Write or
|
||||
// ReadFrom function of the ResponseWriter. ResponseWriters may also write
|
||||
// data to their underlaying connection directly (e.g. headers), but those
|
||||
// are not tracked. Therefor the number of Written bytes will usually match
|
||||
// the size of the response body.
|
||||
Written int64
|
||||
}
|
||||
|
||||
// CaptureMetrics wraps the given hnd, executes it with the given w and r, and
|
||||
// returns the metrics it captured from it.
|
||||
func CaptureMetrics(hnd http.Handler, w http.ResponseWriter, r *http.Request) Metrics {
|
||||
return CaptureMetricsFn(w, func(ww http.ResponseWriter) {
|
||||
hnd.ServeHTTP(ww, r)
|
||||
})
|
||||
}
|
||||
|
||||
// CaptureMetricsFn wraps w and calls fn with the wrapped w and returns the
|
||||
// resulting metrics. This is very similar to CaptureMetrics (which is just
|
||||
// sugar on top of this func), but is a more usable interface if your
|
||||
// application doesn't use the Go http.Handler interface.
|
||||
func CaptureMetricsFn(w http.ResponseWriter, fn func(http.ResponseWriter)) Metrics {
|
||||
m := Metrics{Code: http.StatusOK}
|
||||
m.CaptureMetrics(w, fn)
|
||||
return m
|
||||
}
|
||||
|
||||
// CaptureMetrics wraps w and calls fn with the wrapped w and updates
|
||||
// Metrics m with the resulting metrics. This is similar to CaptureMetricsFn,
|
||||
// but allows one to customize starting Metrics object.
|
||||
func (m *Metrics) CaptureMetrics(w http.ResponseWriter, fn func(http.ResponseWriter)) {
|
||||
var (
|
||||
start = time.Now()
|
||||
headerWritten bool
|
||||
hooks = Hooks{
|
||||
WriteHeader: func(next WriteHeaderFunc) WriteHeaderFunc {
|
||||
return func(code int) {
|
||||
next(code)
|
||||
|
||||
if !(code >= 100 && code <= 199) && !headerWritten {
|
||||
m.Code = code
|
||||
headerWritten = true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Write: func(next WriteFunc) WriteFunc {
|
||||
return func(p []byte) (int, error) {
|
||||
n, err := next(p)
|
||||
|
||||
m.Written += int64(n)
|
||||
headerWritten = true
|
||||
return n, err
|
||||
}
|
||||
},
|
||||
|
||||
ReadFrom: func(next ReadFromFunc) ReadFromFunc {
|
||||
return func(src io.Reader) (int64, error) {
|
||||
n, err := next(src)
|
||||
|
||||
headerWritten = true
|
||||
m.Written += n
|
||||
return n, err
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
fn(Wrap(w, hooks))
|
||||
m.Duration += time.Since(start)
|
||||
}
|
||||
10
src/server/vendor/github.com/felixge/httpsnoop/docs.go
generated
vendored
Normal file
10
src/server/vendor/github.com/felixge/httpsnoop/docs.go
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
// Package httpsnoop provides an easy way to capture http related metrics (i.e.
|
||||
// response time, bytes written, and http status code) from your application's
|
||||
// http.Handlers.
|
||||
//
|
||||
// Doing this requires non-trivial wrapping of the http.ResponseWriter
|
||||
// interface, which is also exposed for users interested in a more low-level
|
||||
// API.
|
||||
package httpsnoop
|
||||
|
||||
//go:generate go run codegen/main.go
|
||||
436
src/server/vendor/github.com/felixge/httpsnoop/wrap_generated_gteq_1.8.go
generated
vendored
Normal file
436
src/server/vendor/github.com/felixge/httpsnoop/wrap_generated_gteq_1.8.go
generated
vendored
Normal file
@ -0,0 +1,436 @@
|
||||
// +build go1.8
|
||||
// Code generated by "httpsnoop/codegen"; DO NOT EDIT.
|
||||
|
||||
package httpsnoop
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// HeaderFunc is part of the http.ResponseWriter interface.
|
||||
type HeaderFunc func() http.Header
|
||||
|
||||
// WriteHeaderFunc is part of the http.ResponseWriter interface.
|
||||
type WriteHeaderFunc func(code int)
|
||||
|
||||
// WriteFunc is part of the http.ResponseWriter interface.
|
||||
type WriteFunc func(b []byte) (int, error)
|
||||
|
||||
// FlushFunc is part of the http.Flusher interface.
|
||||
type FlushFunc func()
|
||||
|
||||
// CloseNotifyFunc is part of the http.CloseNotifier interface.
|
||||
type CloseNotifyFunc func() <-chan bool
|
||||
|
||||
// HijackFunc is part of the http.Hijacker interface.
|
||||
type HijackFunc func() (net.Conn, *bufio.ReadWriter, error)
|
||||
|
||||
// ReadFromFunc is part of the io.ReaderFrom interface.
|
||||
type ReadFromFunc func(src io.Reader) (int64, error)
|
||||
|
||||
// PushFunc is part of the http.Pusher interface.
|
||||
type PushFunc func(target string, opts *http.PushOptions) error
|
||||
|
||||
// Hooks defines a set of method interceptors for methods included in
|
||||
// http.ResponseWriter as well as some others. You can think of them as
|
||||
// middleware for the function calls they target. See Wrap for more details.
|
||||
type Hooks struct {
|
||||
Header func(HeaderFunc) HeaderFunc
|
||||
WriteHeader func(WriteHeaderFunc) WriteHeaderFunc
|
||||
Write func(WriteFunc) WriteFunc
|
||||
Flush func(FlushFunc) FlushFunc
|
||||
CloseNotify func(CloseNotifyFunc) CloseNotifyFunc
|
||||
Hijack func(HijackFunc) HijackFunc
|
||||
ReadFrom func(ReadFromFunc) ReadFromFunc
|
||||
Push func(PushFunc) PushFunc
|
||||
}
|
||||
|
||||
// Wrap returns a wrapped version of w that provides the exact same interface
|
||||
// as w. Specifically if w implements any combination of:
|
||||
//
|
||||
// - http.Flusher
|
||||
// - http.CloseNotifier
|
||||
// - http.Hijacker
|
||||
// - io.ReaderFrom
|
||||
// - http.Pusher
|
||||
//
|
||||
// The wrapped version will implement the exact same combination. If no hooks
|
||||
// are set, the wrapped version also behaves exactly as w. Hooks targeting
|
||||
// methods not supported by w are ignored. Any other hooks will intercept the
|
||||
// method they target and may modify the call's arguments and/or return values.
|
||||
// The CaptureMetrics implementation serves as a working example for how the
|
||||
// hooks can be used.
|
||||
func Wrap(w http.ResponseWriter, hooks Hooks) http.ResponseWriter {
|
||||
rw := &rw{w: w, h: hooks}
|
||||
_, i0 := w.(http.Flusher)
|
||||
_, i1 := w.(http.CloseNotifier)
|
||||
_, i2 := w.(http.Hijacker)
|
||||
_, i3 := w.(io.ReaderFrom)
|
||||
_, i4 := w.(http.Pusher)
|
||||
switch {
|
||||
// combination 1/32
|
||||
case !i0 && !i1 && !i2 && !i3 && !i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
}{rw, rw}
|
||||
// combination 2/32
|
||||
case !i0 && !i1 && !i2 && !i3 && i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Pusher
|
||||
}{rw, rw, rw}
|
||||
// combination 3/32
|
||||
case !i0 && !i1 && !i2 && i3 && !i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
io.ReaderFrom
|
||||
}{rw, rw, rw}
|
||||
// combination 4/32
|
||||
case !i0 && !i1 && !i2 && i3 && i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
io.ReaderFrom
|
||||
http.Pusher
|
||||
}{rw, rw, rw, rw}
|
||||
// combination 5/32
|
||||
case !i0 && !i1 && i2 && !i3 && !i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Hijacker
|
||||
}{rw, rw, rw}
|
||||
// combination 6/32
|
||||
case !i0 && !i1 && i2 && !i3 && i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Hijacker
|
||||
http.Pusher
|
||||
}{rw, rw, rw, rw}
|
||||
// combination 7/32
|
||||
case !i0 && !i1 && i2 && i3 && !i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Hijacker
|
||||
io.ReaderFrom
|
||||
}{rw, rw, rw, rw}
|
||||
// combination 8/32
|
||||
case !i0 && !i1 && i2 && i3 && i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Hijacker
|
||||
io.ReaderFrom
|
||||
http.Pusher
|
||||
}{rw, rw, rw, rw, rw}
|
||||
// combination 9/32
|
||||
case !i0 && i1 && !i2 && !i3 && !i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.CloseNotifier
|
||||
}{rw, rw, rw}
|
||||
// combination 10/32
|
||||
case !i0 && i1 && !i2 && !i3 && i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.CloseNotifier
|
||||
http.Pusher
|
||||
}{rw, rw, rw, rw}
|
||||
// combination 11/32
|
||||
case !i0 && i1 && !i2 && i3 && !i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.CloseNotifier
|
||||
io.ReaderFrom
|
||||
}{rw, rw, rw, rw}
|
||||
// combination 12/32
|
||||
case !i0 && i1 && !i2 && i3 && i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.CloseNotifier
|
||||
io.ReaderFrom
|
||||
http.Pusher
|
||||
}{rw, rw, rw, rw, rw}
|
||||
// combination 13/32
|
||||
case !i0 && i1 && i2 && !i3 && !i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.CloseNotifier
|
||||
http.Hijacker
|
||||
}{rw, rw, rw, rw}
|
||||
// combination 14/32
|
||||
case !i0 && i1 && i2 && !i3 && i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.CloseNotifier
|
||||
http.Hijacker
|
||||
http.Pusher
|
||||
}{rw, rw, rw, rw, rw}
|
||||
// combination 15/32
|
||||
case !i0 && i1 && i2 && i3 && !i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.CloseNotifier
|
||||
http.Hijacker
|
||||
io.ReaderFrom
|
||||
}{rw, rw, rw, rw, rw}
|
||||
// combination 16/32
|
||||
case !i0 && i1 && i2 && i3 && i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.CloseNotifier
|
||||
http.Hijacker
|
||||
io.ReaderFrom
|
||||
http.Pusher
|
||||
}{rw, rw, rw, rw, rw, rw}
|
||||
// combination 17/32
|
||||
case i0 && !i1 && !i2 && !i3 && !i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
}{rw, rw, rw}
|
||||
// combination 18/32
|
||||
case i0 && !i1 && !i2 && !i3 && i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
http.Pusher
|
||||
}{rw, rw, rw, rw}
|
||||
// combination 19/32
|
||||
case i0 && !i1 && !i2 && i3 && !i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
io.ReaderFrom
|
||||
}{rw, rw, rw, rw}
|
||||
// combination 20/32
|
||||
case i0 && !i1 && !i2 && i3 && i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
io.ReaderFrom
|
||||
http.Pusher
|
||||
}{rw, rw, rw, rw, rw}
|
||||
// combination 21/32
|
||||
case i0 && !i1 && i2 && !i3 && !i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
http.Hijacker
|
||||
}{rw, rw, rw, rw}
|
||||
// combination 22/32
|
||||
case i0 && !i1 && i2 && !i3 && i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
http.Hijacker
|
||||
http.Pusher
|
||||
}{rw, rw, rw, rw, rw}
|
||||
// combination 23/32
|
||||
case i0 && !i1 && i2 && i3 && !i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
http.Hijacker
|
||||
io.ReaderFrom
|
||||
}{rw, rw, rw, rw, rw}
|
||||
// combination 24/32
|
||||
case i0 && !i1 && i2 && i3 && i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
http.Hijacker
|
||||
io.ReaderFrom
|
||||
http.Pusher
|
||||
}{rw, rw, rw, rw, rw, rw}
|
||||
// combination 25/32
|
||||
case i0 && i1 && !i2 && !i3 && !i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
http.CloseNotifier
|
||||
}{rw, rw, rw, rw}
|
||||
// combination 26/32
|
||||
case i0 && i1 && !i2 && !i3 && i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
http.CloseNotifier
|
||||
http.Pusher
|
||||
}{rw, rw, rw, rw, rw}
|
||||
// combination 27/32
|
||||
case i0 && i1 && !i2 && i3 && !i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
http.CloseNotifier
|
||||
io.ReaderFrom
|
||||
}{rw, rw, rw, rw, rw}
|
||||
// combination 28/32
|
||||
case i0 && i1 && !i2 && i3 && i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
http.CloseNotifier
|
||||
io.ReaderFrom
|
||||
http.Pusher
|
||||
}{rw, rw, rw, rw, rw, rw}
|
||||
// combination 29/32
|
||||
case i0 && i1 && i2 && !i3 && !i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
http.CloseNotifier
|
||||
http.Hijacker
|
||||
}{rw, rw, rw, rw, rw}
|
||||
// combination 30/32
|
||||
case i0 && i1 && i2 && !i3 && i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
http.CloseNotifier
|
||||
http.Hijacker
|
||||
http.Pusher
|
||||
}{rw, rw, rw, rw, rw, rw}
|
||||
// combination 31/32
|
||||
case i0 && i1 && i2 && i3 && !i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
http.CloseNotifier
|
||||
http.Hijacker
|
||||
io.ReaderFrom
|
||||
}{rw, rw, rw, rw, rw, rw}
|
||||
// combination 32/32
|
||||
case i0 && i1 && i2 && i3 && i4:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
http.CloseNotifier
|
||||
http.Hijacker
|
||||
io.ReaderFrom
|
||||
http.Pusher
|
||||
}{rw, rw, rw, rw, rw, rw, rw}
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
type rw struct {
|
||||
w http.ResponseWriter
|
||||
h Hooks
|
||||
}
|
||||
|
||||
func (w *rw) Unwrap() http.ResponseWriter {
|
||||
return w.w
|
||||
}
|
||||
|
||||
func (w *rw) Header() http.Header {
|
||||
f := w.w.(http.ResponseWriter).Header
|
||||
if w.h.Header != nil {
|
||||
f = w.h.Header(f)
|
||||
}
|
||||
return f()
|
||||
}
|
||||
|
||||
func (w *rw) WriteHeader(code int) {
|
||||
f := w.w.(http.ResponseWriter).WriteHeader
|
||||
if w.h.WriteHeader != nil {
|
||||
f = w.h.WriteHeader(f)
|
||||
}
|
||||
f(code)
|
||||
}
|
||||
|
||||
func (w *rw) Write(b []byte) (int, error) {
|
||||
f := w.w.(http.ResponseWriter).Write
|
||||
if w.h.Write != nil {
|
||||
f = w.h.Write(f)
|
||||
}
|
||||
return f(b)
|
||||
}
|
||||
|
||||
func (w *rw) Flush() {
|
||||
f := w.w.(http.Flusher).Flush
|
||||
if w.h.Flush != nil {
|
||||
f = w.h.Flush(f)
|
||||
}
|
||||
f()
|
||||
}
|
||||
|
||||
func (w *rw) CloseNotify() <-chan bool {
|
||||
f := w.w.(http.CloseNotifier).CloseNotify
|
||||
if w.h.CloseNotify != nil {
|
||||
f = w.h.CloseNotify(f)
|
||||
}
|
||||
return f()
|
||||
}
|
||||
|
||||
func (w *rw) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
f := w.w.(http.Hijacker).Hijack
|
||||
if w.h.Hijack != nil {
|
||||
f = w.h.Hijack(f)
|
||||
}
|
||||
return f()
|
||||
}
|
||||
|
||||
func (w *rw) ReadFrom(src io.Reader) (int64, error) {
|
||||
f := w.w.(io.ReaderFrom).ReadFrom
|
||||
if w.h.ReadFrom != nil {
|
||||
f = w.h.ReadFrom(f)
|
||||
}
|
||||
return f(src)
|
||||
}
|
||||
|
||||
func (w *rw) Push(target string, opts *http.PushOptions) error {
|
||||
f := w.w.(http.Pusher).Push
|
||||
if w.h.Push != nil {
|
||||
f = w.h.Push(f)
|
||||
}
|
||||
return f(target, opts)
|
||||
}
|
||||
|
||||
type Unwrapper interface {
|
||||
Unwrap() http.ResponseWriter
|
||||
}
|
||||
|
||||
// Unwrap returns the underlying http.ResponseWriter from within zero or more
|
||||
// layers of httpsnoop wrappers.
|
||||
func Unwrap(w http.ResponseWriter) http.ResponseWriter {
|
||||
if rw, ok := w.(Unwrapper); ok {
|
||||
// recurse until rw.Unwrap() returns a non-Unwrapper
|
||||
return Unwrap(rw.Unwrap())
|
||||
} else {
|
||||
return w
|
||||
}
|
||||
}
|
||||
278
src/server/vendor/github.com/felixge/httpsnoop/wrap_generated_lt_1.8.go
generated
vendored
Normal file
278
src/server/vendor/github.com/felixge/httpsnoop/wrap_generated_lt_1.8.go
generated
vendored
Normal file
@ -0,0 +1,278 @@
|
||||
// +build !go1.8
|
||||
// Code generated by "httpsnoop/codegen"; DO NOT EDIT.
|
||||
|
||||
package httpsnoop
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// HeaderFunc is part of the http.ResponseWriter interface.
|
||||
type HeaderFunc func() http.Header
|
||||
|
||||
// WriteHeaderFunc is part of the http.ResponseWriter interface.
|
||||
type WriteHeaderFunc func(code int)
|
||||
|
||||
// WriteFunc is part of the http.ResponseWriter interface.
|
||||
type WriteFunc func(b []byte) (int, error)
|
||||
|
||||
// FlushFunc is part of the http.Flusher interface.
|
||||
type FlushFunc func()
|
||||
|
||||
// CloseNotifyFunc is part of the http.CloseNotifier interface.
|
||||
type CloseNotifyFunc func() <-chan bool
|
||||
|
||||
// HijackFunc is part of the http.Hijacker interface.
|
||||
type HijackFunc func() (net.Conn, *bufio.ReadWriter, error)
|
||||
|
||||
// ReadFromFunc is part of the io.ReaderFrom interface.
|
||||
type ReadFromFunc func(src io.Reader) (int64, error)
|
||||
|
||||
// Hooks defines a set of method interceptors for methods included in
|
||||
// http.ResponseWriter as well as some others. You can think of them as
|
||||
// middleware for the function calls they target. See Wrap for more details.
|
||||
type Hooks struct {
|
||||
Header func(HeaderFunc) HeaderFunc
|
||||
WriteHeader func(WriteHeaderFunc) WriteHeaderFunc
|
||||
Write func(WriteFunc) WriteFunc
|
||||
Flush func(FlushFunc) FlushFunc
|
||||
CloseNotify func(CloseNotifyFunc) CloseNotifyFunc
|
||||
Hijack func(HijackFunc) HijackFunc
|
||||
ReadFrom func(ReadFromFunc) ReadFromFunc
|
||||
}
|
||||
|
||||
// Wrap returns a wrapped version of w that provides the exact same interface
|
||||
// as w. Specifically if w implements any combination of:
|
||||
//
|
||||
// - http.Flusher
|
||||
// - http.CloseNotifier
|
||||
// - http.Hijacker
|
||||
// - io.ReaderFrom
|
||||
//
|
||||
// The wrapped version will implement the exact same combination. If no hooks
|
||||
// are set, the wrapped version also behaves exactly as w. Hooks targeting
|
||||
// methods not supported by w are ignored. Any other hooks will intercept the
|
||||
// method they target and may modify the call's arguments and/or return values.
|
||||
// The CaptureMetrics implementation serves as a working example for how the
|
||||
// hooks can be used.
|
||||
func Wrap(w http.ResponseWriter, hooks Hooks) http.ResponseWriter {
|
||||
rw := &rw{w: w, h: hooks}
|
||||
_, i0 := w.(http.Flusher)
|
||||
_, i1 := w.(http.CloseNotifier)
|
||||
_, i2 := w.(http.Hijacker)
|
||||
_, i3 := w.(io.ReaderFrom)
|
||||
switch {
|
||||
// combination 1/16
|
||||
case !i0 && !i1 && !i2 && !i3:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
}{rw, rw}
|
||||
// combination 2/16
|
||||
case !i0 && !i1 && !i2 && i3:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
io.ReaderFrom
|
||||
}{rw, rw, rw}
|
||||
// combination 3/16
|
||||
case !i0 && !i1 && i2 && !i3:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Hijacker
|
||||
}{rw, rw, rw}
|
||||
// combination 4/16
|
||||
case !i0 && !i1 && i2 && i3:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Hijacker
|
||||
io.ReaderFrom
|
||||
}{rw, rw, rw, rw}
|
||||
// combination 5/16
|
||||
case !i0 && i1 && !i2 && !i3:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.CloseNotifier
|
||||
}{rw, rw, rw}
|
||||
// combination 6/16
|
||||
case !i0 && i1 && !i2 && i3:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.CloseNotifier
|
||||
io.ReaderFrom
|
||||
}{rw, rw, rw, rw}
|
||||
// combination 7/16
|
||||
case !i0 && i1 && i2 && !i3:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.CloseNotifier
|
||||
http.Hijacker
|
||||
}{rw, rw, rw, rw}
|
||||
// combination 8/16
|
||||
case !i0 && i1 && i2 && i3:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.CloseNotifier
|
||||
http.Hijacker
|
||||
io.ReaderFrom
|
||||
}{rw, rw, rw, rw, rw}
|
||||
// combination 9/16
|
||||
case i0 && !i1 && !i2 && !i3:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
}{rw, rw, rw}
|
||||
// combination 10/16
|
||||
case i0 && !i1 && !i2 && i3:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
io.ReaderFrom
|
||||
}{rw, rw, rw, rw}
|
||||
// combination 11/16
|
||||
case i0 && !i1 && i2 && !i3:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
http.Hijacker
|
||||
}{rw, rw, rw, rw}
|
||||
// combination 12/16
|
||||
case i0 && !i1 && i2 && i3:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
http.Hijacker
|
||||
io.ReaderFrom
|
||||
}{rw, rw, rw, rw, rw}
|
||||
// combination 13/16
|
||||
case i0 && i1 && !i2 && !i3:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
http.CloseNotifier
|
||||
}{rw, rw, rw, rw}
|
||||
// combination 14/16
|
||||
case i0 && i1 && !i2 && i3:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
http.CloseNotifier
|
||||
io.ReaderFrom
|
||||
}{rw, rw, rw, rw, rw}
|
||||
// combination 15/16
|
||||
case i0 && i1 && i2 && !i3:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
http.CloseNotifier
|
||||
http.Hijacker
|
||||
}{rw, rw, rw, rw, rw}
|
||||
// combination 16/16
|
||||
case i0 && i1 && i2 && i3:
|
||||
return struct {
|
||||
Unwrapper
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
http.CloseNotifier
|
||||
http.Hijacker
|
||||
io.ReaderFrom
|
||||
}{rw, rw, rw, rw, rw, rw}
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
type rw struct {
|
||||
w http.ResponseWriter
|
||||
h Hooks
|
||||
}
|
||||
|
||||
func (w *rw) Unwrap() http.ResponseWriter {
|
||||
return w.w
|
||||
}
|
||||
|
||||
func (w *rw) Header() http.Header {
|
||||
f := w.w.(http.ResponseWriter).Header
|
||||
if w.h.Header != nil {
|
||||
f = w.h.Header(f)
|
||||
}
|
||||
return f()
|
||||
}
|
||||
|
||||
func (w *rw) WriteHeader(code int) {
|
||||
f := w.w.(http.ResponseWriter).WriteHeader
|
||||
if w.h.WriteHeader != nil {
|
||||
f = w.h.WriteHeader(f)
|
||||
}
|
||||
f(code)
|
||||
}
|
||||
|
||||
func (w *rw) Write(b []byte) (int, error) {
|
||||
f := w.w.(http.ResponseWriter).Write
|
||||
if w.h.Write != nil {
|
||||
f = w.h.Write(f)
|
||||
}
|
||||
return f(b)
|
||||
}
|
||||
|
||||
func (w *rw) Flush() {
|
||||
f := w.w.(http.Flusher).Flush
|
||||
if w.h.Flush != nil {
|
||||
f = w.h.Flush(f)
|
||||
}
|
||||
f()
|
||||
}
|
||||
|
||||
func (w *rw) CloseNotify() <-chan bool {
|
||||
f := w.w.(http.CloseNotifier).CloseNotify
|
||||
if w.h.CloseNotify != nil {
|
||||
f = w.h.CloseNotify(f)
|
||||
}
|
||||
return f()
|
||||
}
|
||||
|
||||
func (w *rw) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
f := w.w.(http.Hijacker).Hijack
|
||||
if w.h.Hijack != nil {
|
||||
f = w.h.Hijack(f)
|
||||
}
|
||||
return f()
|
||||
}
|
||||
|
||||
func (w *rw) ReadFrom(src io.Reader) (int64, error) {
|
||||
f := w.w.(io.ReaderFrom).ReadFrom
|
||||
if w.h.ReadFrom != nil {
|
||||
f = w.h.ReadFrom(f)
|
||||
}
|
||||
return f(src)
|
||||
}
|
||||
|
||||
type Unwrapper interface {
|
||||
Unwrap() http.ResponseWriter
|
||||
}
|
||||
|
||||
// Unwrap returns the underlying http.ResponseWriter from within zero or more
|
||||
// layers of httpsnoop wrappers.
|
||||
func Unwrap(w http.ResponseWriter) http.ResponseWriter {
|
||||
if rw, ok := w.(Unwrapper); ok {
|
||||
// recurse until rw.Unwrap() returns a non-Unwrapper
|
||||
return Unwrap(rw.Unwrap())
|
||||
} else {
|
||||
return w
|
||||
}
|
||||
}
|
||||
26
src/server/vendor/github.com/go-logr/logr/.golangci.yaml
generated
vendored
Normal file
26
src/server/vendor/github.com/go-logr/logr/.golangci.yaml
generated
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
run:
|
||||
timeout: 1m
|
||||
tests: true
|
||||
|
||||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
- asciicheck
|
||||
- errcheck
|
||||
- forcetypeassert
|
||||
- gocritic
|
||||
- gofmt
|
||||
- goimports
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- misspell
|
||||
- revive
|
||||
- staticcheck
|
||||
- typecheck
|
||||
- unused
|
||||
|
||||
issues:
|
||||
exclude-use-default: false
|
||||
max-issues-per-linter: 0
|
||||
max-same-issues: 10
|
||||
6
src/server/vendor/github.com/go-logr/logr/CHANGELOG.md
generated
vendored
Normal file
6
src/server/vendor/github.com/go-logr/logr/CHANGELOG.md
generated
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
# CHANGELOG
|
||||
|
||||
## v1.0.0-rc1
|
||||
|
||||
This is the first logged release. Major changes (including breaking changes)
|
||||
have occurred since earlier tags.
|
||||
17
src/server/vendor/github.com/go-logr/logr/CONTRIBUTING.md
generated
vendored
Normal file
17
src/server/vendor/github.com/go-logr/logr/CONTRIBUTING.md
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
# Contributing
|
||||
|
||||
Logr is open to pull-requests, provided they fit within the intended scope of
|
||||
the project. Specifically, this library aims to be VERY small and minimalist,
|
||||
with no external dependencies.
|
||||
|
||||
## Compatibility
|
||||
|
||||
This project intends to follow [semantic versioning](http://semver.org) and
|
||||
is very strict about compatibility. Any proposed changes MUST follow those
|
||||
rules.
|
||||
|
||||
## Performance
|
||||
|
||||
As a logging library, logr must be as light-weight as possible. Any proposed
|
||||
code change must include results of running the [benchmark](./benchmark)
|
||||
before and after the change.
|
||||
201
src/server/vendor/github.com/go-logr/logr/LICENSE
generated
vendored
Normal file
201
src/server/vendor/github.com/go-logr/logr/LICENSE
generated
vendored
Normal file
@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
407
src/server/vendor/github.com/go-logr/logr/README.md
generated
vendored
Normal file
407
src/server/vendor/github.com/go-logr/logr/README.md
generated
vendored
Normal file
@ -0,0 +1,407 @@
|
||||
# A minimal logging API for Go
|
||||
|
||||
[](https://pkg.go.dev/github.com/go-logr/logr)
|
||||
[](https://goreportcard.com/report/github.com/go-logr/logr)
|
||||
[](https://securityscorecards.dev/viewer/?platform=github.com&org=go-logr&repo=logr)
|
||||
|
||||
logr offers an(other) opinion on how Go programs and libraries can do logging
|
||||
without becoming coupled to a particular logging implementation. This is not
|
||||
an implementation of logging - it is an API. In fact it is two APIs with two
|
||||
different sets of users.
|
||||
|
||||
The `Logger` type is intended for application and library authors. It provides
|
||||
a relatively small API which can be used everywhere you want to emit logs. It
|
||||
defers the actual act of writing logs (to files, to stdout, or whatever) to the
|
||||
`LogSink` interface.
|
||||
|
||||
The `LogSink` interface is intended for logging library implementers. It is a
|
||||
pure interface which can be implemented by logging frameworks to provide the actual logging
|
||||
functionality.
|
||||
|
||||
This decoupling allows application and library developers to write code in
|
||||
terms of `logr.Logger` (which has very low dependency fan-out) while the
|
||||
implementation of logging is managed "up stack" (e.g. in or near `main()`.)
|
||||
Application developers can then switch out implementations as necessary.
|
||||
|
||||
Many people assert that libraries should not be logging, and as such efforts
|
||||
like this are pointless. Those people are welcome to convince the authors of
|
||||
the tens-of-thousands of libraries that *DO* write logs that they are all
|
||||
wrong. In the meantime, logr takes a more practical approach.
|
||||
|
||||
## Typical usage
|
||||
|
||||
Somewhere, early in an application's life, it will make a decision about which
|
||||
logging library (implementation) it actually wants to use. Something like:
|
||||
|
||||
```
|
||||
func main() {
|
||||
// ... other setup code ...
|
||||
|
||||
// Create the "root" logger. We have chosen the "logimpl" implementation,
|
||||
// which takes some initial parameters and returns a logr.Logger.
|
||||
logger := logimpl.New(param1, param2)
|
||||
|
||||
// ... other setup code ...
|
||||
```
|
||||
|
||||
Most apps will call into other libraries, create structures to govern the flow,
|
||||
etc. The `logr.Logger` object can be passed to these other libraries, stored
|
||||
in structs, or even used as a package-global variable, if needed. For example:
|
||||
|
||||
```
|
||||
app := createTheAppObject(logger)
|
||||
app.Run()
|
||||
```
|
||||
|
||||
Outside of this early setup, no other packages need to know about the choice of
|
||||
implementation. They write logs in terms of the `logr.Logger` that they
|
||||
received:
|
||||
|
||||
```
|
||||
type appObject struct {
|
||||
// ... other fields ...
|
||||
logger logr.Logger
|
||||
// ... other fields ...
|
||||
}
|
||||
|
||||
func (app *appObject) Run() {
|
||||
app.logger.Info("starting up", "timestamp", time.Now())
|
||||
|
||||
// ... app code ...
|
||||
```
|
||||
|
||||
## Background
|
||||
|
||||
If the Go standard library had defined an interface for logging, this project
|
||||
probably would not be needed. Alas, here we are.
|
||||
|
||||
When the Go developers started developing such an interface with
|
||||
[slog](https://github.com/golang/go/issues/56345), they adopted some of the
|
||||
logr design but also left out some parts and changed others:
|
||||
|
||||
| Feature | logr | slog |
|
||||
|---------|------|------|
|
||||
| High-level API | `Logger` (passed by value) | `Logger` (passed by [pointer](https://github.com/golang/go/issues/59126)) |
|
||||
| Low-level API | `LogSink` | `Handler` |
|
||||
| Stack unwinding | done by `LogSink` | done by `Logger` |
|
||||
| Skipping helper functions | `WithCallDepth`, `WithCallStackHelper` | [not supported by Logger](https://github.com/golang/go/issues/59145) |
|
||||
| Generating a value for logging on demand | `Marshaler` | `LogValuer` |
|
||||
| Log levels | >= 0, higher meaning "less important" | positive and negative, with 0 for "info" and higher meaning "more important" |
|
||||
| Error log entries | always logged, don't have a verbosity level | normal log entries with level >= `LevelError` |
|
||||
| Passing logger via context | `NewContext`, `FromContext` | no API |
|
||||
| Adding a name to a logger | `WithName` | no API |
|
||||
| Modify verbosity of log entries in a call chain | `V` | no API |
|
||||
| Grouping of key/value pairs | not supported | `WithGroup`, `GroupValue` |
|
||||
| Pass context for extracting additional values | no API | API variants like `InfoCtx` |
|
||||
|
||||
The high-level slog API is explicitly meant to be one of many different APIs
|
||||
that can be layered on top of a shared `slog.Handler`. logr is one such
|
||||
alternative API, with [interoperability](#slog-interoperability) provided by
|
||||
some conversion functions.
|
||||
|
||||
### Inspiration
|
||||
|
||||
Before you consider this package, please read [this blog post by the
|
||||
inimitable Dave Cheney][warning-makes-no-sense]. We really appreciate what
|
||||
he has to say, and it largely aligns with our own experiences.
|
||||
|
||||
### Differences from Dave's ideas
|
||||
|
||||
The main differences are:
|
||||
|
||||
1. Dave basically proposes doing away with the notion of a logging API in favor
|
||||
of `fmt.Printf()`. We disagree, especially when you consider things like output
|
||||
locations, timestamps, file and line decorations, and structured logging. This
|
||||
package restricts the logging API to just 2 types of logs: info and error.
|
||||
|
||||
Info logs are things you want to tell the user which are not errors. Error
|
||||
logs are, well, errors. If your code receives an `error` from a subordinate
|
||||
function call and is logging that `error` *and not returning it*, use error
|
||||
logs.
|
||||
|
||||
2. Verbosity-levels on info logs. This gives developers a chance to indicate
|
||||
arbitrary grades of importance for info logs, without assigning names with
|
||||
semantic meaning such as "warning", "trace", and "debug." Superficially this
|
||||
may feel very similar, but the primary difference is the lack of semantics.
|
||||
Because verbosity is a numerical value, it's safe to assume that an app running
|
||||
with higher verbosity means more (and less important) logs will be generated.
|
||||
|
||||
## Implementations (non-exhaustive)
|
||||
|
||||
There are implementations for the following logging libraries:
|
||||
|
||||
- **a function** (can bridge to non-structured libraries): [funcr](https://github.com/go-logr/logr/tree/master/funcr)
|
||||
- **a testing.T** (for use in Go tests, with JSON-like output): [testr](https://github.com/go-logr/logr/tree/master/testr)
|
||||
- **github.com/google/glog**: [glogr](https://github.com/go-logr/glogr)
|
||||
- **k8s.io/klog** (for Kubernetes): [klogr](https://git.k8s.io/klog/klogr)
|
||||
- **a testing.T** (with klog-like text output): [ktesting](https://git.k8s.io/klog/ktesting)
|
||||
- **go.uber.org/zap**: [zapr](https://github.com/go-logr/zapr)
|
||||
- **log** (the Go standard library logger): [stdr](https://github.com/go-logr/stdr)
|
||||
- **github.com/sirupsen/logrus**: [logrusr](https://github.com/bombsimon/logrusr)
|
||||
- **github.com/wojas/genericr**: [genericr](https://github.com/wojas/genericr) (makes it easy to implement your own backend)
|
||||
- **logfmt** (Heroku style [logging](https://www.brandur.org/logfmt)): [logfmtr](https://github.com/iand/logfmtr)
|
||||
- **github.com/rs/zerolog**: [zerologr](https://github.com/go-logr/zerologr)
|
||||
- **github.com/go-kit/log**: [gokitlogr](https://github.com/tonglil/gokitlogr) (also compatible with github.com/go-kit/kit/log since v0.12.0)
|
||||
- **bytes.Buffer** (writing to a buffer): [bufrlogr](https://github.com/tonglil/buflogr) (useful for ensuring values were logged, like during testing)
|
||||
|
||||
## slog interoperability
|
||||
|
||||
Interoperability goes both ways, using the `logr.Logger` API with a `slog.Handler`
|
||||
and using the `slog.Logger` API with a `logr.LogSink`. `FromSlogHandler` and
|
||||
`ToSlogHandler` convert between a `logr.Logger` and a `slog.Handler`.
|
||||
As usual, `slog.New` can be used to wrap such a `slog.Handler` in the high-level
|
||||
slog API.
|
||||
|
||||
### Using a `logr.LogSink` as backend for slog
|
||||
|
||||
Ideally, a logr sink implementation should support both logr and slog by
|
||||
implementing both the normal logr interface(s) and `SlogSink`. Because
|
||||
of a conflict in the parameters of the common `Enabled` method, it is [not
|
||||
possible to implement both slog.Handler and logr.Sink in the same
|
||||
type](https://github.com/golang/go/issues/59110).
|
||||
|
||||
If both are supported, log calls can go from the high-level APIs to the backend
|
||||
without the need to convert parameters. `FromSlogHandler` and `ToSlogHandler` can
|
||||
convert back and forth without adding additional wrappers, with one exception:
|
||||
when `Logger.V` was used to adjust the verbosity for a `slog.Handler`, then
|
||||
`ToSlogHandler` has to use a wrapper which adjusts the verbosity for future
|
||||
log calls.
|
||||
|
||||
Such an implementation should also support values that implement specific
|
||||
interfaces from both packages for logging (`logr.Marshaler`, `slog.LogValuer`,
|
||||
`slog.GroupValue`). logr does not convert those.
|
||||
|
||||
Not supporting slog has several drawbacks:
|
||||
- Recording source code locations works correctly if the handler gets called
|
||||
through `slog.Logger`, but may be wrong in other cases. That's because a
|
||||
`logr.Sink` does its own stack unwinding instead of using the program counter
|
||||
provided by the high-level API.
|
||||
- slog levels <= 0 can be mapped to logr levels by negating the level without a
|
||||
loss of information. But all slog levels > 0 (e.g. `slog.LevelWarning` as
|
||||
used by `slog.Logger.Warn`) must be mapped to 0 before calling the sink
|
||||
because logr does not support "more important than info" levels.
|
||||
- The slog group concept is supported by prefixing each key in a key/value
|
||||
pair with the group names, separated by a dot. For structured output like
|
||||
JSON it would be better to group the key/value pairs inside an object.
|
||||
- Special slog values and interfaces don't work as expected.
|
||||
- The overhead is likely to be higher.
|
||||
|
||||
These drawbacks are severe enough that applications using a mixture of slog and
|
||||
logr should switch to a different backend.
|
||||
|
||||
### Using a `slog.Handler` as backend for logr
|
||||
|
||||
Using a plain `slog.Handler` without support for logr works better than the
|
||||
other direction:
|
||||
- All logr verbosity levels can be mapped 1:1 to their corresponding slog level
|
||||
by negating them.
|
||||
- Stack unwinding is done by the `SlogSink` and the resulting program
|
||||
counter is passed to the `slog.Handler`.
|
||||
- Names added via `Logger.WithName` are gathered and recorded in an additional
|
||||
attribute with `logger` as key and the names separated by slash as value.
|
||||
- `Logger.Error` is turned into a log record with `slog.LevelError` as level
|
||||
and an additional attribute with `err` as key, if an error was provided.
|
||||
|
||||
The main drawback is that `logr.Marshaler` will not be supported. Types should
|
||||
ideally support both `logr.Marshaler` and `slog.Valuer`. If compatibility
|
||||
with logr implementations without slog support is not important, then
|
||||
`slog.Valuer` is sufficient.
|
||||
|
||||
### Context support for slog
|
||||
|
||||
Storing a logger in a `context.Context` is not supported by
|
||||
slog. `NewContextWithSlogLogger` and `FromContextAsSlogLogger` can be
|
||||
used to fill this gap. They store and retrieve a `slog.Logger` pointer
|
||||
under the same context key that is also used by `NewContext` and
|
||||
`FromContext` for `logr.Logger` value.
|
||||
|
||||
When `NewContextWithSlogLogger` is followed by `FromContext`, the latter will
|
||||
automatically convert the `slog.Logger` to a
|
||||
`logr.Logger`. `FromContextAsSlogLogger` does the same for the other direction.
|
||||
|
||||
With this approach, binaries which use either slog or logr are as efficient as
|
||||
possible with no unnecessary allocations. This is also why the API stores a
|
||||
`slog.Logger` pointer: when storing a `slog.Handler`, creating a `slog.Logger`
|
||||
on retrieval would need to allocate one.
|
||||
|
||||
The downside is that switching back and forth needs more allocations. Because
|
||||
logr is the API that is already in use by different packages, in particular
|
||||
Kubernetes, the recommendation is to use the `logr.Logger` API in code which
|
||||
uses contextual logging.
|
||||
|
||||
An alternative to adding values to a logger and storing that logger in the
|
||||
context is to store the values in the context and to configure a logging
|
||||
backend to extract those values when emitting log entries. This only works when
|
||||
log calls are passed the context, which is not supported by the logr API.
|
||||
|
||||
With the slog API, it is possible, but not
|
||||
required. https://github.com/veqryn/slog-context is a package for slog which
|
||||
provides additional support code for this approach. It also contains wrappers
|
||||
for the context functions in logr, so developers who prefer to not use the logr
|
||||
APIs directly can use those instead and the resulting code will still be
|
||||
interoperable with logr.
|
||||
|
||||
## FAQ
|
||||
|
||||
### Conceptual
|
||||
|
||||
#### Why structured logging?
|
||||
|
||||
- **Structured logs are more easily queryable**: Since you've got
|
||||
key-value pairs, it's much easier to query your structured logs for
|
||||
particular values by filtering on the contents of a particular key --
|
||||
think searching request logs for error codes, Kubernetes reconcilers for
|
||||
the name and namespace of the reconciled object, etc.
|
||||
|
||||
- **Structured logging makes it easier to have cross-referenceable logs**:
|
||||
Similarly to searchability, if you maintain conventions around your
|
||||
keys, it becomes easy to gather all log lines related to a particular
|
||||
concept.
|
||||
|
||||
- **Structured logs allow better dimensions of filtering**: if you have
|
||||
structure to your logs, you've got more precise control over how much
|
||||
information is logged -- you might choose in a particular configuration
|
||||
to log certain keys but not others, only log lines where a certain key
|
||||
matches a certain value, etc., instead of just having v-levels and names
|
||||
to key off of.
|
||||
|
||||
- **Structured logs better represent structured data**: sometimes, the
|
||||
data that you want to log is inherently structured (think tuple-link
|
||||
objects.) Structured logs allow you to preserve that structure when
|
||||
outputting.
|
||||
|
||||
#### Why V-levels?
|
||||
|
||||
**V-levels give operators an easy way to control the chattiness of log
|
||||
operations**. V-levels provide a way for a given package to distinguish
|
||||
the relative importance or verbosity of a given log message. Then, if
|
||||
a particular logger or package is logging too many messages, the user
|
||||
of the package can simply change the v-levels for that library.
|
||||
|
||||
#### Why not named levels, like Info/Warning/Error?
|
||||
|
||||
Read [Dave Cheney's post][warning-makes-no-sense]. Then read [Differences
|
||||
from Dave's ideas](#differences-from-daves-ideas).
|
||||
|
||||
#### Why not allow format strings, too?
|
||||
|
||||
**Format strings negate many of the benefits of structured logs**:
|
||||
|
||||
- They're not easily searchable without resorting to fuzzy searching,
|
||||
regular expressions, etc.
|
||||
|
||||
- They don't store structured data well, since contents are flattened into
|
||||
a string.
|
||||
|
||||
- They're not cross-referenceable.
|
||||
|
||||
- They don't compress easily, since the message is not constant.
|
||||
|
||||
(Unless you turn positional parameters into key-value pairs with numerical
|
||||
keys, at which point you've gotten key-value logging with meaningless
|
||||
keys.)
|
||||
|
||||
### Practical
|
||||
|
||||
#### Why key-value pairs, and not a map?
|
||||
|
||||
Key-value pairs are *much* easier to optimize, especially around
|
||||
allocations. Zap (a structured logger that inspired logr's interface) has
|
||||
[performance measurements](https://github.com/uber-go/zap#performance)
|
||||
that show this quite nicely.
|
||||
|
||||
While the interface ends up being a little less obvious, you get
|
||||
potentially better performance, plus avoid making users type
|
||||
`map[string]string{}` every time they want to log.
|
||||
|
||||
#### What if my V-levels differ between libraries?
|
||||
|
||||
That's fine. Control your V-levels on a per-logger basis, and use the
|
||||
`WithName` method to pass different loggers to different libraries.
|
||||
|
||||
Generally, you should take care to ensure that you have relatively
|
||||
consistent V-levels within a given logger, however, as this makes deciding
|
||||
on what verbosity of logs to request easier.
|
||||
|
||||
#### But I really want to use a format string!
|
||||
|
||||
That's not actually a question. Assuming your question is "how do
|
||||
I convert my mental model of logging with format strings to logging with
|
||||
constant messages":
|
||||
|
||||
1. Figure out what the error actually is, as you'd write in a TL;DR style,
|
||||
and use that as a message.
|
||||
|
||||
2. For every place you'd write a format specifier, look to the word before
|
||||
it, and add that as a key value pair.
|
||||
|
||||
For instance, consider the following examples (all taken from spots in the
|
||||
Kubernetes codebase):
|
||||
|
||||
- `klog.V(4).Infof("Client is returning errors: code %v, error %v",
|
||||
responseCode, err)` becomes `logger.Error(err, "client returned an
|
||||
error", "code", responseCode)`
|
||||
|
||||
- `klog.V(4).Infof("Got a Retry-After %ds response for attempt %d to %v",
|
||||
seconds, retries, url)` becomes `logger.V(4).Info("got a retry-after
|
||||
response when requesting url", "attempt", retries, "after
|
||||
seconds", seconds, "url", url)`
|
||||
|
||||
If you *really* must use a format string, use it in a key's value, and
|
||||
call `fmt.Sprintf` yourself. For instance: `log.Printf("unable to
|
||||
reflect over type %T")` becomes `logger.Info("unable to reflect over
|
||||
type", "type", fmt.Sprintf("%T"))`. In general though, the cases where
|
||||
this is necessary should be few and far between.
|
||||
|
||||
#### How do I choose my V-levels?
|
||||
|
||||
This is basically the only hard constraint: increase V-levels to denote
|
||||
more verbose or more debug-y logs.
|
||||
|
||||
Otherwise, you can start out with `0` as "you always want to see this",
|
||||
`1` as "common logging that you might *possibly* want to turn off", and
|
||||
`10` as "I would like to performance-test your log collection stack."
|
||||
|
||||
Then gradually choose levels in between as you need them, working your way
|
||||
down from 10 (for debug and trace style logs) and up from 1 (for chattier
|
||||
info-type logs). For reference, slog pre-defines -4 for debug logs
|
||||
(corresponds to 4 in logr), which matches what is
|
||||
[recommended for Kubernetes](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/logging.md#what-method-to-use).
|
||||
|
||||
#### How do I choose my keys?
|
||||
|
||||
Keys are fairly flexible, and can hold more or less any string
|
||||
value. For best compatibility with implementations and consistency
|
||||
with existing code in other projects, there are a few conventions you
|
||||
should consider.
|
||||
|
||||
- Make your keys human-readable.
|
||||
- Constant keys are generally a good idea.
|
||||
- Be consistent across your codebase.
|
||||
- Keys should naturally match parts of the message string.
|
||||
- Use lower case for simple keys and
|
||||
[lowerCamelCase](https://en.wiktionary.org/wiki/lowerCamelCase) for
|
||||
more complex ones. Kubernetes is one example of a project that has
|
||||
[adopted that
|
||||
convention](https://github.com/kubernetes/community/blob/HEAD/contributors/devel/sig-instrumentation/migration-to-structured-logging.md#name-arguments).
|
||||
|
||||
While key names are mostly unrestricted (and spaces are acceptable),
|
||||
it's generally a good idea to stick to printable ascii characters, or at
|
||||
least match the general character set of your log lines.
|
||||
|
||||
#### Why should keys be constant values?
|
||||
|
||||
The point of structured logging is to make later log processing easier. Your
|
||||
keys are, effectively, the schema of each log message. If you use different
|
||||
keys across instances of the same log line, you will make your structured logs
|
||||
much harder to use. `Sprintf()` is for values, not for keys!
|
||||
|
||||
#### Why is this not a pure interface?
|
||||
|
||||
The Logger type is implemented as a struct in order to allow the Go compiler to
|
||||
optimize things like high-V `Info` logs that are not triggered. Not all of
|
||||
these implementations are implemented yet, but this structure was suggested as
|
||||
a way to ensure they *can* be implemented. All of the real work is behind the
|
||||
`LogSink` interface.
|
||||
|
||||
[warning-makes-no-sense]: http://dave.cheney.net/2015/11/05/lets-talk-about-logging
|
||||
18
src/server/vendor/github.com/go-logr/logr/SECURITY.md
generated
vendored
Normal file
18
src/server/vendor/github.com/go-logr/logr/SECURITY.md
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
# Security Policy
|
||||
|
||||
If you have discovered a security vulnerability in this project, please report it
|
||||
privately. **Do not disclose it as a public issue.** This gives us time to work with you
|
||||
to fix the issue before public exposure, reducing the chance that the exploit will be
|
||||
used before a patch is released.
|
||||
|
||||
You may submit the report in the following ways:
|
||||
|
||||
- send an email to go-logr-security@googlegroups.com
|
||||
- send us a [private vulnerability report](https://github.com/go-logr/logr/security/advisories/new)
|
||||
|
||||
Please provide the following information in your report:
|
||||
|
||||
- A description of the vulnerability and its impact
|
||||
- How to reproduce the issue
|
||||
|
||||
We ask that you give us 90 days to work on a fix before public exposure.
|
||||
33
src/server/vendor/github.com/go-logr/logr/context.go
generated
vendored
Normal file
33
src/server/vendor/github.com/go-logr/logr/context.go
generated
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
/*
|
||||
Copyright 2023 The logr Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package logr
|
||||
|
||||
// contextKey is how we find Loggers in a context.Context. With Go < 1.21,
|
||||
// the value is always a Logger value. With Go >= 1.21, the value can be a
|
||||
// Logger value or a slog.Logger pointer.
|
||||
type contextKey struct{}
|
||||
|
||||
// notFoundError exists to carry an IsNotFound method.
|
||||
type notFoundError struct{}
|
||||
|
||||
func (notFoundError) Error() string {
|
||||
return "no logr.Logger was present"
|
||||
}
|
||||
|
||||
func (notFoundError) IsNotFound() bool {
|
||||
return true
|
||||
}
|
||||
49
src/server/vendor/github.com/go-logr/logr/context_noslog.go
generated
vendored
Normal file
49
src/server/vendor/github.com/go-logr/logr/context_noslog.go
generated
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
//go:build !go1.21
|
||||
// +build !go1.21
|
||||
|
||||
/*
|
||||
Copyright 2019 The logr Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package logr
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// FromContext returns a Logger from ctx or an error if no Logger is found.
|
||||
func FromContext(ctx context.Context) (Logger, error) {
|
||||
if v, ok := ctx.Value(contextKey{}).(Logger); ok {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
return Logger{}, notFoundError{}
|
||||
}
|
||||
|
||||
// FromContextOrDiscard returns a Logger from ctx. If no Logger is found, this
|
||||
// returns a Logger that discards all log messages.
|
||||
func FromContextOrDiscard(ctx context.Context) Logger {
|
||||
if v, ok := ctx.Value(contextKey{}).(Logger); ok {
|
||||
return v
|
||||
}
|
||||
|
||||
return Discard()
|
||||
}
|
||||
|
||||
// NewContext returns a new Context, derived from ctx, which carries the
|
||||
// provided Logger.
|
||||
func NewContext(ctx context.Context, logger Logger) context.Context {
|
||||
return context.WithValue(ctx, contextKey{}, logger)
|
||||
}
|
||||
83
src/server/vendor/github.com/go-logr/logr/context_slog.go
generated
vendored
Normal file
83
src/server/vendor/github.com/go-logr/logr/context_slog.go
generated
vendored
Normal file
@ -0,0 +1,83 @@
|
||||
//go:build go1.21
|
||||
// +build go1.21
|
||||
|
||||
/*
|
||||
Copyright 2019 The logr Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package logr
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
// FromContext returns a Logger from ctx or an error if no Logger is found.
|
||||
func FromContext(ctx context.Context) (Logger, error) {
|
||||
v := ctx.Value(contextKey{})
|
||||
if v == nil {
|
||||
return Logger{}, notFoundError{}
|
||||
}
|
||||
|
||||
switch v := v.(type) {
|
||||
case Logger:
|
||||
return v, nil
|
||||
case *slog.Logger:
|
||||
return FromSlogHandler(v.Handler()), nil
|
||||
default:
|
||||
// Not reached.
|
||||
panic(fmt.Sprintf("unexpected value type for logr context key: %T", v))
|
||||
}
|
||||
}
|
||||
|
||||
// FromContextAsSlogLogger returns a slog.Logger from ctx or nil if no such Logger is found.
|
||||
func FromContextAsSlogLogger(ctx context.Context) *slog.Logger {
|
||||
v := ctx.Value(contextKey{})
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch v := v.(type) {
|
||||
case Logger:
|
||||
return slog.New(ToSlogHandler(v))
|
||||
case *slog.Logger:
|
||||
return v
|
||||
default:
|
||||
// Not reached.
|
||||
panic(fmt.Sprintf("unexpected value type for logr context key: %T", v))
|
||||
}
|
||||
}
|
||||
|
||||
// FromContextOrDiscard returns a Logger from ctx. If no Logger is found, this
|
||||
// returns a Logger that discards all log messages.
|
||||
func FromContextOrDiscard(ctx context.Context) Logger {
|
||||
if logger, err := FromContext(ctx); err == nil {
|
||||
return logger
|
||||
}
|
||||
return Discard()
|
||||
}
|
||||
|
||||
// NewContext returns a new Context, derived from ctx, which carries the
|
||||
// provided Logger.
|
||||
func NewContext(ctx context.Context, logger Logger) context.Context {
|
||||
return context.WithValue(ctx, contextKey{}, logger)
|
||||
}
|
||||
|
||||
// NewContextWithSlogLogger returns a new Context, derived from ctx, which carries the
|
||||
// provided slog.Logger.
|
||||
func NewContextWithSlogLogger(ctx context.Context, logger *slog.Logger) context.Context {
|
||||
return context.WithValue(ctx, contextKey{}, logger)
|
||||
}
|
||||
24
src/server/vendor/github.com/go-logr/logr/discard.go
generated
vendored
Normal file
24
src/server/vendor/github.com/go-logr/logr/discard.go
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
/*
|
||||
Copyright 2020 The logr Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package logr
|
||||
|
||||
// Discard returns a Logger that discards all messages logged to it. It can be
|
||||
// used whenever the caller is not interested in the logs. Logger instances
|
||||
// produced by this function always compare as equal.
|
||||
func Discard() Logger {
|
||||
return New(nil)
|
||||
}
|
||||
914
src/server/vendor/github.com/go-logr/logr/funcr/funcr.go
generated
vendored
Normal file
914
src/server/vendor/github.com/go-logr/logr/funcr/funcr.go
generated
vendored
Normal file
@ -0,0 +1,914 @@
|
||||
/*
|
||||
Copyright 2021 The logr Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package funcr implements formatting of structured log messages and
|
||||
// optionally captures the call site and timestamp.
|
||||
//
|
||||
// The simplest way to use it is via its implementation of a
|
||||
// github.com/go-logr/logr.LogSink with output through an arbitrary
|
||||
// "write" function. See New and NewJSON for details.
|
||||
//
|
||||
// # Custom LogSinks
|
||||
//
|
||||
// For users who need more control, a funcr.Formatter can be embedded inside
|
||||
// your own custom LogSink implementation. This is useful when the LogSink
|
||||
// needs to implement additional methods, for example.
|
||||
//
|
||||
// # Formatting
|
||||
//
|
||||
// This will respect logr.Marshaler, fmt.Stringer, and error interfaces for
|
||||
// values which are being logged. When rendering a struct, funcr will use Go's
|
||||
// standard JSON tags (all except "string").
|
||||
package funcr
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
)
|
||||
|
||||
// New returns a logr.Logger which is implemented by an arbitrary function.
|
||||
func New(fn func(prefix, args string), opts Options) logr.Logger {
|
||||
return logr.New(newSink(fn, NewFormatter(opts)))
|
||||
}
|
||||
|
||||
// NewJSON returns a logr.Logger which is implemented by an arbitrary function
|
||||
// and produces JSON output.
|
||||
func NewJSON(fn func(obj string), opts Options) logr.Logger {
|
||||
fnWrapper := func(_, obj string) {
|
||||
fn(obj)
|
||||
}
|
||||
return logr.New(newSink(fnWrapper, NewFormatterJSON(opts)))
|
||||
}
|
||||
|
||||
// Underlier exposes access to the underlying logging function. Since
|
||||
// callers only have a logr.Logger, they have to know which
|
||||
// implementation is in use, so this interface is less of an
|
||||
// abstraction and more of a way to test type conversion.
|
||||
type Underlier interface {
|
||||
GetUnderlying() func(prefix, args string)
|
||||
}
|
||||
|
||||
func newSink(fn func(prefix, args string), formatter Formatter) logr.LogSink {
|
||||
l := &fnlogger{
|
||||
Formatter: formatter,
|
||||
write: fn,
|
||||
}
|
||||
// For skipping fnlogger.Info and fnlogger.Error.
|
||||
l.Formatter.AddCallDepth(1)
|
||||
return l
|
||||
}
|
||||
|
||||
// Options carries parameters which influence the way logs are generated.
|
||||
type Options struct {
|
||||
// LogCaller tells funcr to add a "caller" key to some or all log lines.
|
||||
// This has some overhead, so some users might not want it.
|
||||
LogCaller MessageClass
|
||||
|
||||
// LogCallerFunc tells funcr to also log the calling function name. This
|
||||
// has no effect if caller logging is not enabled (see Options.LogCaller).
|
||||
LogCallerFunc bool
|
||||
|
||||
// LogTimestamp tells funcr to add a "ts" key to log lines. This has some
|
||||
// overhead, so some users might not want it.
|
||||
LogTimestamp bool
|
||||
|
||||
// TimestampFormat tells funcr how to render timestamps when LogTimestamp
|
||||
// is enabled. If not specified, a default format will be used. For more
|
||||
// details, see docs for Go's time.Layout.
|
||||
TimestampFormat string
|
||||
|
||||
// LogInfoLevel tells funcr what key to use to log the info level.
|
||||
// If not specified, the info level will be logged as "level".
|
||||
// If this is set to "", the info level will not be logged at all.
|
||||
LogInfoLevel *string
|
||||
|
||||
// Verbosity tells funcr which V logs to produce. Higher values enable
|
||||
// more logs. Info logs at or below this level will be written, while logs
|
||||
// above this level will be discarded.
|
||||
Verbosity int
|
||||
|
||||
// RenderBuiltinsHook allows users to mutate the list of key-value pairs
|
||||
// while a log line is being rendered. The kvList argument follows logr
|
||||
// conventions - each pair of slice elements is comprised of a string key
|
||||
// and an arbitrary value (verified and sanitized before calling this
|
||||
// hook). The value returned must follow the same conventions. This hook
|
||||
// can be used to audit or modify logged data. For example, you might want
|
||||
// to prefix all of funcr's built-in keys with some string. This hook is
|
||||
// only called for built-in (provided by funcr itself) key-value pairs.
|
||||
// Equivalent hooks are offered for key-value pairs saved via
|
||||
// logr.Logger.WithValues or Formatter.AddValues (see RenderValuesHook) and
|
||||
// for user-provided pairs (see RenderArgsHook).
|
||||
RenderBuiltinsHook func(kvList []any) []any
|
||||
|
||||
// RenderValuesHook is the same as RenderBuiltinsHook, except that it is
|
||||
// only called for key-value pairs saved via logr.Logger.WithValues. See
|
||||
// RenderBuiltinsHook for more details.
|
||||
RenderValuesHook func(kvList []any) []any
|
||||
|
||||
// RenderArgsHook is the same as RenderBuiltinsHook, except that it is only
|
||||
// called for key-value pairs passed directly to Info and Error. See
|
||||
// RenderBuiltinsHook for more details.
|
||||
RenderArgsHook func(kvList []any) []any
|
||||
|
||||
// MaxLogDepth tells funcr how many levels of nested fields (e.g. a struct
|
||||
// that contains a struct, etc.) it may log. Every time it finds a struct,
|
||||
// slice, array, or map the depth is increased by one. When the maximum is
|
||||
// reached, the value will be converted to a string indicating that the max
|
||||
// depth has been exceeded. If this field is not specified, a default
|
||||
// value will be used.
|
||||
MaxLogDepth int
|
||||
}
|
||||
|
||||
// MessageClass indicates which category or categories of messages to consider.
|
||||
type MessageClass int
|
||||
|
||||
const (
|
||||
// None ignores all message classes.
|
||||
None MessageClass = iota
|
||||
// All considers all message classes.
|
||||
All
|
||||
// Info only considers info messages.
|
||||
Info
|
||||
// Error only considers error messages.
|
||||
Error
|
||||
)
|
||||
|
||||
// fnlogger inherits some of its LogSink implementation from Formatter
|
||||
// and just needs to add some glue code.
|
||||
type fnlogger struct {
|
||||
Formatter
|
||||
write func(prefix, args string)
|
||||
}
|
||||
|
||||
func (l fnlogger) WithName(name string) logr.LogSink {
|
||||
l.Formatter.AddName(name)
|
||||
return &l
|
||||
}
|
||||
|
||||
func (l fnlogger) WithValues(kvList ...any) logr.LogSink {
|
||||
l.Formatter.AddValues(kvList)
|
||||
return &l
|
||||
}
|
||||
|
||||
func (l fnlogger) WithCallDepth(depth int) logr.LogSink {
|
||||
l.Formatter.AddCallDepth(depth)
|
||||
return &l
|
||||
}
|
||||
|
||||
func (l fnlogger) Info(level int, msg string, kvList ...any) {
|
||||
prefix, args := l.FormatInfo(level, msg, kvList)
|
||||
l.write(prefix, args)
|
||||
}
|
||||
|
||||
func (l fnlogger) Error(err error, msg string, kvList ...any) {
|
||||
prefix, args := l.FormatError(err, msg, kvList)
|
||||
l.write(prefix, args)
|
||||
}
|
||||
|
||||
func (l fnlogger) GetUnderlying() func(prefix, args string) {
|
||||
return l.write
|
||||
}
|
||||
|
||||
// Assert conformance to the interfaces.
|
||||
var _ logr.LogSink = &fnlogger{}
|
||||
var _ logr.CallDepthLogSink = &fnlogger{}
|
||||
var _ Underlier = &fnlogger{}
|
||||
|
||||
// NewFormatter constructs a Formatter which emits a JSON-like key=value format.
|
||||
func NewFormatter(opts Options) Formatter {
|
||||
return newFormatter(opts, outputKeyValue)
|
||||
}
|
||||
|
||||
// NewFormatterJSON constructs a Formatter which emits strict JSON.
|
||||
func NewFormatterJSON(opts Options) Formatter {
|
||||
return newFormatter(opts, outputJSON)
|
||||
}
|
||||
|
||||
// Defaults for Options.
|
||||
const defaultTimestampFormat = "2006-01-02 15:04:05.000000"
|
||||
const defaultMaxLogDepth = 16
|
||||
|
||||
func newFormatter(opts Options, outfmt outputFormat) Formatter {
|
||||
if opts.TimestampFormat == "" {
|
||||
opts.TimestampFormat = defaultTimestampFormat
|
||||
}
|
||||
if opts.MaxLogDepth == 0 {
|
||||
opts.MaxLogDepth = defaultMaxLogDepth
|
||||
}
|
||||
if opts.LogInfoLevel == nil {
|
||||
opts.LogInfoLevel = new(string)
|
||||
*opts.LogInfoLevel = "level"
|
||||
}
|
||||
f := Formatter{
|
||||
outputFormat: outfmt,
|
||||
prefix: "",
|
||||
values: nil,
|
||||
depth: 0,
|
||||
opts: &opts,
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
// Formatter is an opaque struct which can be embedded in a LogSink
|
||||
// implementation. It should be constructed with NewFormatter. Some of
|
||||
// its methods directly implement logr.LogSink.
|
||||
type Formatter struct {
|
||||
outputFormat outputFormat
|
||||
prefix string
|
||||
values []any
|
||||
valuesStr string
|
||||
depth int
|
||||
opts *Options
|
||||
groupName string // for slog groups
|
||||
groups []groupDef
|
||||
}
|
||||
|
||||
// outputFormat indicates which outputFormat to use.
|
||||
type outputFormat int
|
||||
|
||||
const (
|
||||
// outputKeyValue emits a JSON-like key=value format, but not strict JSON.
|
||||
outputKeyValue outputFormat = iota
|
||||
// outputJSON emits strict JSON.
|
||||
outputJSON
|
||||
)
|
||||
|
||||
// groupDef represents a saved group. The values may be empty, but we don't
|
||||
// know if we need to render the group until the final record is rendered.
|
||||
type groupDef struct {
|
||||
name string
|
||||
values string
|
||||
}
|
||||
|
||||
// PseudoStruct is a list of key-value pairs that gets logged as a struct.
|
||||
type PseudoStruct []any
|
||||
|
||||
// render produces a log line, ready to use.
|
||||
func (f Formatter) render(builtins, args []any) string {
|
||||
// Empirically bytes.Buffer is faster than strings.Builder for this.
|
||||
buf := bytes.NewBuffer(make([]byte, 0, 1024))
|
||||
|
||||
if f.outputFormat == outputJSON {
|
||||
buf.WriteByte('{') // for the whole record
|
||||
}
|
||||
|
||||
// Render builtins
|
||||
vals := builtins
|
||||
if hook := f.opts.RenderBuiltinsHook; hook != nil {
|
||||
vals = hook(f.sanitize(vals))
|
||||
}
|
||||
f.flatten(buf, vals, false) // keys are ours, no need to escape
|
||||
continuing := len(builtins) > 0
|
||||
|
||||
// Turn the inner-most group into a string
|
||||
argsStr := func() string {
|
||||
buf := bytes.NewBuffer(make([]byte, 0, 1024))
|
||||
|
||||
vals = args
|
||||
if hook := f.opts.RenderArgsHook; hook != nil {
|
||||
vals = hook(f.sanitize(vals))
|
||||
}
|
||||
f.flatten(buf, vals, true) // escape user-provided keys
|
||||
|
||||
return buf.String()
|
||||
}()
|
||||
|
||||
// Render the stack of groups from the inside out.
|
||||
bodyStr := f.renderGroup(f.groupName, f.valuesStr, argsStr)
|
||||
for i := len(f.groups) - 1; i >= 0; i-- {
|
||||
grp := &f.groups[i]
|
||||
if grp.values == "" && bodyStr == "" {
|
||||
// no contents, so we must elide the whole group
|
||||
continue
|
||||
}
|
||||
bodyStr = f.renderGroup(grp.name, grp.values, bodyStr)
|
||||
}
|
||||
|
||||
if bodyStr != "" {
|
||||
if continuing {
|
||||
buf.WriteByte(f.comma())
|
||||
}
|
||||
buf.WriteString(bodyStr)
|
||||
}
|
||||
|
||||
if f.outputFormat == outputJSON {
|
||||
buf.WriteByte('}') // for the whole record
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// renderGroup returns a string representation of the named group with rendered
|
||||
// values and args. If the name is empty, this will return the values and args,
|
||||
// joined. If the name is not empty, this will return a single key-value pair,
|
||||
// where the value is a grouping of the values and args. If the values and
|
||||
// args are both empty, this will return an empty string, even if the name was
|
||||
// specified.
|
||||
func (f Formatter) renderGroup(name string, values string, args string) string {
|
||||
buf := bytes.NewBuffer(make([]byte, 0, 1024))
|
||||
|
||||
needClosingBrace := false
|
||||
if name != "" && (values != "" || args != "") {
|
||||
buf.WriteString(f.quoted(name, true)) // escape user-provided keys
|
||||
buf.WriteByte(f.colon())
|
||||
buf.WriteByte('{')
|
||||
needClosingBrace = true
|
||||
}
|
||||
|
||||
continuing := false
|
||||
if values != "" {
|
||||
buf.WriteString(values)
|
||||
continuing = true
|
||||
}
|
||||
|
||||
if args != "" {
|
||||
if continuing {
|
||||
buf.WriteByte(f.comma())
|
||||
}
|
||||
buf.WriteString(args)
|
||||
}
|
||||
|
||||
if needClosingBrace {
|
||||
buf.WriteByte('}')
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// flatten renders a list of key-value pairs into a buffer. If escapeKeys is
|
||||
// true, the keys are assumed to have non-JSON-compatible characters in them
|
||||
// and must be evaluated for escapes.
|
||||
//
|
||||
// This function returns a potentially modified version of kvList, which
|
||||
// ensures that there is a value for every key (adding a value if needed) and
|
||||
// that each key is a string (substituting a key if needed).
|
||||
func (f Formatter) flatten(buf *bytes.Buffer, kvList []any, escapeKeys bool) []any {
|
||||
// This logic overlaps with sanitize() but saves one type-cast per key,
|
||||
// which can be measurable.
|
||||
if len(kvList)%2 != 0 {
|
||||
kvList = append(kvList, noValue)
|
||||
}
|
||||
copied := false
|
||||
for i := 0; i < len(kvList); i += 2 {
|
||||
k, ok := kvList[i].(string)
|
||||
if !ok {
|
||||
if !copied {
|
||||
newList := make([]any, len(kvList))
|
||||
copy(newList, kvList)
|
||||
kvList = newList
|
||||
copied = true
|
||||
}
|
||||
k = f.nonStringKey(kvList[i])
|
||||
kvList[i] = k
|
||||
}
|
||||
v := kvList[i+1]
|
||||
|
||||
if i > 0 {
|
||||
if f.outputFormat == outputJSON {
|
||||
buf.WriteByte(f.comma())
|
||||
} else {
|
||||
// In theory the format could be something we don't understand. In
|
||||
// practice, we control it, so it won't be.
|
||||
buf.WriteByte(' ')
|
||||
}
|
||||
}
|
||||
|
||||
buf.WriteString(f.quoted(k, escapeKeys))
|
||||
buf.WriteByte(f.colon())
|
||||
buf.WriteString(f.pretty(v))
|
||||
}
|
||||
return kvList
|
||||
}
|
||||
|
||||
func (f Formatter) quoted(str string, escape bool) string {
|
||||
if escape {
|
||||
return prettyString(str)
|
||||
}
|
||||
// this is faster
|
||||
return `"` + str + `"`
|
||||
}
|
||||
|
||||
func (f Formatter) comma() byte {
|
||||
if f.outputFormat == outputJSON {
|
||||
return ','
|
||||
}
|
||||
return ' '
|
||||
}
|
||||
|
||||
func (f Formatter) colon() byte {
|
||||
if f.outputFormat == outputJSON {
|
||||
return ':'
|
||||
}
|
||||
return '='
|
||||
}
|
||||
|
||||
func (f Formatter) pretty(value any) string {
|
||||
return f.prettyWithFlags(value, 0, 0)
|
||||
}
|
||||
|
||||
const (
|
||||
flagRawStruct = 0x1 // do not print braces on structs
|
||||
)
|
||||
|
||||
// TODO: This is not fast. Most of the overhead goes here.
|
||||
func (f Formatter) prettyWithFlags(value any, flags uint32, depth int) string {
|
||||
if depth > f.opts.MaxLogDepth {
|
||||
return `"<max-log-depth-exceeded>"`
|
||||
}
|
||||
|
||||
// Handle types that take full control of logging.
|
||||
if v, ok := value.(logr.Marshaler); ok {
|
||||
// Replace the value with what the type wants to get logged.
|
||||
// That then gets handled below via reflection.
|
||||
value = invokeMarshaler(v)
|
||||
}
|
||||
|
||||
// Handle types that want to format themselves.
|
||||
switch v := value.(type) {
|
||||
case fmt.Stringer:
|
||||
value = invokeStringer(v)
|
||||
case error:
|
||||
value = invokeError(v)
|
||||
}
|
||||
|
||||
// Handling the most common types without reflect is a small perf win.
|
||||
switch v := value.(type) {
|
||||
case bool:
|
||||
return strconv.FormatBool(v)
|
||||
case string:
|
||||
return prettyString(v)
|
||||
case int:
|
||||
return strconv.FormatInt(int64(v), 10)
|
||||
case int8:
|
||||
return strconv.FormatInt(int64(v), 10)
|
||||
case int16:
|
||||
return strconv.FormatInt(int64(v), 10)
|
||||
case int32:
|
||||
return strconv.FormatInt(int64(v), 10)
|
||||
case int64:
|
||||
return strconv.FormatInt(int64(v), 10)
|
||||
case uint:
|
||||
return strconv.FormatUint(uint64(v), 10)
|
||||
case uint8:
|
||||
return strconv.FormatUint(uint64(v), 10)
|
||||
case uint16:
|
||||
return strconv.FormatUint(uint64(v), 10)
|
||||
case uint32:
|
||||
return strconv.FormatUint(uint64(v), 10)
|
||||
case uint64:
|
||||
return strconv.FormatUint(v, 10)
|
||||
case uintptr:
|
||||
return strconv.FormatUint(uint64(v), 10)
|
||||
case float32:
|
||||
return strconv.FormatFloat(float64(v), 'f', -1, 32)
|
||||
case float64:
|
||||
return strconv.FormatFloat(v, 'f', -1, 64)
|
||||
case complex64:
|
||||
return `"` + strconv.FormatComplex(complex128(v), 'f', -1, 64) + `"`
|
||||
case complex128:
|
||||
return `"` + strconv.FormatComplex(v, 'f', -1, 128) + `"`
|
||||
case PseudoStruct:
|
||||
buf := bytes.NewBuffer(make([]byte, 0, 1024))
|
||||
v = f.sanitize(v)
|
||||
if flags&flagRawStruct == 0 {
|
||||
buf.WriteByte('{')
|
||||
}
|
||||
for i := 0; i < len(v); i += 2 {
|
||||
if i > 0 {
|
||||
buf.WriteByte(f.comma())
|
||||
}
|
||||
k, _ := v[i].(string) // sanitize() above means no need to check success
|
||||
// arbitrary keys might need escaping
|
||||
buf.WriteString(prettyString(k))
|
||||
buf.WriteByte(f.colon())
|
||||
buf.WriteString(f.prettyWithFlags(v[i+1], 0, depth+1))
|
||||
}
|
||||
if flags&flagRawStruct == 0 {
|
||||
buf.WriteByte('}')
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(make([]byte, 0, 256))
|
||||
t := reflect.TypeOf(value)
|
||||
if t == nil {
|
||||
return "null"
|
||||
}
|
||||
v := reflect.ValueOf(value)
|
||||
switch t.Kind() {
|
||||
case reflect.Bool:
|
||||
return strconv.FormatBool(v.Bool())
|
||||
case reflect.String:
|
||||
return prettyString(v.String())
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return strconv.FormatInt(int64(v.Int()), 10)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return strconv.FormatUint(uint64(v.Uint()), 10)
|
||||
case reflect.Float32:
|
||||
return strconv.FormatFloat(float64(v.Float()), 'f', -1, 32)
|
||||
case reflect.Float64:
|
||||
return strconv.FormatFloat(v.Float(), 'f', -1, 64)
|
||||
case reflect.Complex64:
|
||||
return `"` + strconv.FormatComplex(complex128(v.Complex()), 'f', -1, 64) + `"`
|
||||
case reflect.Complex128:
|
||||
return `"` + strconv.FormatComplex(v.Complex(), 'f', -1, 128) + `"`
|
||||
case reflect.Struct:
|
||||
if flags&flagRawStruct == 0 {
|
||||
buf.WriteByte('{')
|
||||
}
|
||||
printComma := false // testing i>0 is not enough because of JSON omitted fields
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
fld := t.Field(i)
|
||||
if fld.PkgPath != "" {
|
||||
// reflect says this field is only defined for non-exported fields.
|
||||
continue
|
||||
}
|
||||
if !v.Field(i).CanInterface() {
|
||||
// reflect isn't clear exactly what this means, but we can't use it.
|
||||
continue
|
||||
}
|
||||
name := ""
|
||||
omitempty := false
|
||||
if tag, found := fld.Tag.Lookup("json"); found {
|
||||
if tag == "-" {
|
||||
continue
|
||||
}
|
||||
if comma := strings.Index(tag, ","); comma != -1 {
|
||||
if n := tag[:comma]; n != "" {
|
||||
name = n
|
||||
}
|
||||
rest := tag[comma:]
|
||||
if strings.Contains(rest, ",omitempty,") || strings.HasSuffix(rest, ",omitempty") {
|
||||
omitempty = true
|
||||
}
|
||||
} else {
|
||||
name = tag
|
||||
}
|
||||
}
|
||||
if omitempty && isEmpty(v.Field(i)) {
|
||||
continue
|
||||
}
|
||||
if printComma {
|
||||
buf.WriteByte(f.comma())
|
||||
}
|
||||
printComma = true // if we got here, we are rendering a field
|
||||
if fld.Anonymous && fld.Type.Kind() == reflect.Struct && name == "" {
|
||||
buf.WriteString(f.prettyWithFlags(v.Field(i).Interface(), flags|flagRawStruct, depth+1))
|
||||
continue
|
||||
}
|
||||
if name == "" {
|
||||
name = fld.Name
|
||||
}
|
||||
// field names can't contain characters which need escaping
|
||||
buf.WriteString(f.quoted(name, false))
|
||||
buf.WriteByte(f.colon())
|
||||
buf.WriteString(f.prettyWithFlags(v.Field(i).Interface(), 0, depth+1))
|
||||
}
|
||||
if flags&flagRawStruct == 0 {
|
||||
buf.WriteByte('}')
|
||||
}
|
||||
return buf.String()
|
||||
case reflect.Slice, reflect.Array:
|
||||
// If this is outputing as JSON make sure this isn't really a json.RawMessage.
|
||||
// If so just emit "as-is" and don't pretty it as that will just print
|
||||
// it as [X,Y,Z,...] which isn't terribly useful vs the string form you really want.
|
||||
if f.outputFormat == outputJSON {
|
||||
if rm, ok := value.(json.RawMessage); ok {
|
||||
// If it's empty make sure we emit an empty value as the array style would below.
|
||||
if len(rm) > 0 {
|
||||
buf.Write(rm)
|
||||
} else {
|
||||
buf.WriteString("null")
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
}
|
||||
buf.WriteByte('[')
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
if i > 0 {
|
||||
buf.WriteByte(f.comma())
|
||||
}
|
||||
e := v.Index(i)
|
||||
buf.WriteString(f.prettyWithFlags(e.Interface(), 0, depth+1))
|
||||
}
|
||||
buf.WriteByte(']')
|
||||
return buf.String()
|
||||
case reflect.Map:
|
||||
buf.WriteByte('{')
|
||||
// This does not sort the map keys, for best perf.
|
||||
it := v.MapRange()
|
||||
i := 0
|
||||
for it.Next() {
|
||||
if i > 0 {
|
||||
buf.WriteByte(f.comma())
|
||||
}
|
||||
// If a map key supports TextMarshaler, use it.
|
||||
keystr := ""
|
||||
if m, ok := it.Key().Interface().(encoding.TextMarshaler); ok {
|
||||
txt, err := m.MarshalText()
|
||||
if err != nil {
|
||||
keystr = fmt.Sprintf("<error-MarshalText: %s>", err.Error())
|
||||
} else {
|
||||
keystr = string(txt)
|
||||
}
|
||||
keystr = prettyString(keystr)
|
||||
} else {
|
||||
// prettyWithFlags will produce already-escaped values
|
||||
keystr = f.prettyWithFlags(it.Key().Interface(), 0, depth+1)
|
||||
if t.Key().Kind() != reflect.String {
|
||||
// JSON only does string keys. Unlike Go's standard JSON, we'll
|
||||
// convert just about anything to a string.
|
||||
keystr = prettyString(keystr)
|
||||
}
|
||||
}
|
||||
buf.WriteString(keystr)
|
||||
buf.WriteByte(f.colon())
|
||||
buf.WriteString(f.prettyWithFlags(it.Value().Interface(), 0, depth+1))
|
||||
i++
|
||||
}
|
||||
buf.WriteByte('}')
|
||||
return buf.String()
|
||||
case reflect.Ptr, reflect.Interface:
|
||||
if v.IsNil() {
|
||||
return "null"
|
||||
}
|
||||
return f.prettyWithFlags(v.Elem().Interface(), 0, depth)
|
||||
}
|
||||
return fmt.Sprintf(`"<unhandled-%s>"`, t.Kind().String())
|
||||
}
|
||||
|
||||
func prettyString(s string) string {
|
||||
// Avoid escaping (which does allocations) if we can.
|
||||
if needsEscape(s) {
|
||||
return strconv.Quote(s)
|
||||
}
|
||||
b := bytes.NewBuffer(make([]byte, 0, 1024))
|
||||
b.WriteByte('"')
|
||||
b.WriteString(s)
|
||||
b.WriteByte('"')
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// needsEscape determines whether the input string needs to be escaped or not,
|
||||
// without doing any allocations.
|
||||
func needsEscape(s string) bool {
|
||||
for _, r := range s {
|
||||
if !strconv.IsPrint(r) || r == '\\' || r == '"' {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isEmpty(v reflect.Value) bool {
|
||||
switch v.Kind() {
|
||||
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
||||
return v.Len() == 0
|
||||
case reflect.Bool:
|
||||
return !v.Bool()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return v.Int() == 0
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return v.Uint() == 0
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return v.Float() == 0
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
return v.Complex() == 0
|
||||
case reflect.Interface, reflect.Ptr:
|
||||
return v.IsNil()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func invokeMarshaler(m logr.Marshaler) (ret any) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ret = fmt.Sprintf("<panic: %s>", r)
|
||||
}
|
||||
}()
|
||||
return m.MarshalLog()
|
||||
}
|
||||
|
||||
func invokeStringer(s fmt.Stringer) (ret string) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ret = fmt.Sprintf("<panic: %s>", r)
|
||||
}
|
||||
}()
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func invokeError(e error) (ret string) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ret = fmt.Sprintf("<panic: %s>", r)
|
||||
}
|
||||
}()
|
||||
return e.Error()
|
||||
}
|
||||
|
||||
// Caller represents the original call site for a log line, after considering
|
||||
// logr.Logger.WithCallDepth and logr.Logger.WithCallStackHelper. The File and
|
||||
// Line fields will always be provided, while the Func field is optional.
|
||||
// Users can set the render hook fields in Options to examine logged key-value
|
||||
// pairs, one of which will be {"caller", Caller} if the Options.LogCaller
|
||||
// field is enabled for the given MessageClass.
|
||||
type Caller struct {
|
||||
// File is the basename of the file for this call site.
|
||||
File string `json:"file"`
|
||||
// Line is the line number in the file for this call site.
|
||||
Line int `json:"line"`
|
||||
// Func is the function name for this call site, or empty if
|
||||
// Options.LogCallerFunc is not enabled.
|
||||
Func string `json:"function,omitempty"`
|
||||
}
|
||||
|
||||
func (f Formatter) caller() Caller {
|
||||
// +1 for this frame, +1 for Info/Error.
|
||||
pc, file, line, ok := runtime.Caller(f.depth + 2)
|
||||
if !ok {
|
||||
return Caller{"<unknown>", 0, ""}
|
||||
}
|
||||
fn := ""
|
||||
if f.opts.LogCallerFunc {
|
||||
if fp := runtime.FuncForPC(pc); fp != nil {
|
||||
fn = fp.Name()
|
||||
}
|
||||
}
|
||||
|
||||
return Caller{filepath.Base(file), line, fn}
|
||||
}
|
||||
|
||||
const noValue = "<no-value>"
|
||||
|
||||
func (f Formatter) nonStringKey(v any) string {
|
||||
return fmt.Sprintf("<non-string-key: %s>", f.snippet(v))
|
||||
}
|
||||
|
||||
// snippet produces a short snippet string of an arbitrary value.
|
||||
func (f Formatter) snippet(v any) string {
|
||||
const snipLen = 16
|
||||
|
||||
snip := f.pretty(v)
|
||||
if len(snip) > snipLen {
|
||||
snip = snip[:snipLen]
|
||||
}
|
||||
return snip
|
||||
}
|
||||
|
||||
// sanitize ensures that a list of key-value pairs has a value for every key
|
||||
// (adding a value if needed) and that each key is a string (substituting a key
|
||||
// if needed).
|
||||
func (f Formatter) sanitize(kvList []any) []any {
|
||||
if len(kvList)%2 != 0 {
|
||||
kvList = append(kvList, noValue)
|
||||
}
|
||||
for i := 0; i < len(kvList); i += 2 {
|
||||
_, ok := kvList[i].(string)
|
||||
if !ok {
|
||||
kvList[i] = f.nonStringKey(kvList[i])
|
||||
}
|
||||
}
|
||||
return kvList
|
||||
}
|
||||
|
||||
// startGroup opens a new group scope (basically a sub-struct), which locks all
|
||||
// the current saved values and starts them anew. This is needed to satisfy
|
||||
// slog.
|
||||
func (f *Formatter) startGroup(name string) {
|
||||
// Unnamed groups are just inlined.
|
||||
if name == "" {
|
||||
return
|
||||
}
|
||||
|
||||
n := len(f.groups)
|
||||
f.groups = append(f.groups[:n:n], groupDef{f.groupName, f.valuesStr})
|
||||
|
||||
// Start collecting new values.
|
||||
f.groupName = name
|
||||
f.valuesStr = ""
|
||||
f.values = nil
|
||||
}
|
||||
|
||||
// Init configures this Formatter from runtime info, such as the call depth
|
||||
// imposed by logr itself.
|
||||
// Note that this receiver is a pointer, so depth can be saved.
|
||||
func (f *Formatter) Init(info logr.RuntimeInfo) {
|
||||
f.depth += info.CallDepth
|
||||
}
|
||||
|
||||
// Enabled checks whether an info message at the given level should be logged.
|
||||
func (f Formatter) Enabled(level int) bool {
|
||||
return level <= f.opts.Verbosity
|
||||
}
|
||||
|
||||
// GetDepth returns the current depth of this Formatter. This is useful for
|
||||
// implementations which do their own caller attribution.
|
||||
func (f Formatter) GetDepth() int {
|
||||
return f.depth
|
||||
}
|
||||
|
||||
// FormatInfo renders an Info log message into strings. The prefix will be
|
||||
// empty when no names were set (via AddNames), or when the output is
|
||||
// configured for JSON.
|
||||
func (f Formatter) FormatInfo(level int, msg string, kvList []any) (prefix, argsStr string) {
|
||||
args := make([]any, 0, 64) // using a constant here impacts perf
|
||||
prefix = f.prefix
|
||||
if f.outputFormat == outputJSON {
|
||||
args = append(args, "logger", prefix)
|
||||
prefix = ""
|
||||
}
|
||||
if f.opts.LogTimestamp {
|
||||
args = append(args, "ts", time.Now().Format(f.opts.TimestampFormat))
|
||||
}
|
||||
if policy := f.opts.LogCaller; policy == All || policy == Info {
|
||||
args = append(args, "caller", f.caller())
|
||||
}
|
||||
if key := *f.opts.LogInfoLevel; key != "" {
|
||||
args = append(args, key, level)
|
||||
}
|
||||
args = append(args, "msg", msg)
|
||||
return prefix, f.render(args, kvList)
|
||||
}
|
||||
|
||||
// FormatError renders an Error log message into strings. The prefix will be
|
||||
// empty when no names were set (via AddNames), or when the output is
|
||||
// configured for JSON.
|
||||
func (f Formatter) FormatError(err error, msg string, kvList []any) (prefix, argsStr string) {
|
||||
args := make([]any, 0, 64) // using a constant here impacts perf
|
||||
prefix = f.prefix
|
||||
if f.outputFormat == outputJSON {
|
||||
args = append(args, "logger", prefix)
|
||||
prefix = ""
|
||||
}
|
||||
if f.opts.LogTimestamp {
|
||||
args = append(args, "ts", time.Now().Format(f.opts.TimestampFormat))
|
||||
}
|
||||
if policy := f.opts.LogCaller; policy == All || policy == Error {
|
||||
args = append(args, "caller", f.caller())
|
||||
}
|
||||
args = append(args, "msg", msg)
|
||||
var loggableErr any
|
||||
if err != nil {
|
||||
loggableErr = err.Error()
|
||||
}
|
||||
args = append(args, "error", loggableErr)
|
||||
return prefix, f.render(args, kvList)
|
||||
}
|
||||
|
||||
// AddName appends the specified name. funcr uses '/' characters to separate
|
||||
// name elements. Callers should not pass '/' in the provided name string, but
|
||||
// this library does not actually enforce that.
|
||||
func (f *Formatter) AddName(name string) {
|
||||
if len(f.prefix) > 0 {
|
||||
f.prefix += "/"
|
||||
}
|
||||
f.prefix += name
|
||||
}
|
||||
|
||||
// AddValues adds key-value pairs to the set of saved values to be logged with
|
||||
// each log line.
|
||||
func (f *Formatter) AddValues(kvList []any) {
|
||||
// Three slice args forces a copy.
|
||||
n := len(f.values)
|
||||
f.values = append(f.values[:n:n], kvList...)
|
||||
|
||||
vals := f.values
|
||||
if hook := f.opts.RenderValuesHook; hook != nil {
|
||||
vals = hook(f.sanitize(vals))
|
||||
}
|
||||
|
||||
// Pre-render values, so we don't have to do it on each Info/Error call.
|
||||
buf := bytes.NewBuffer(make([]byte, 0, 1024))
|
||||
f.flatten(buf, vals, true) // escape user-provided keys
|
||||
f.valuesStr = buf.String()
|
||||
}
|
||||
|
||||
// AddCallDepth increases the number of stack-frames to skip when attributing
|
||||
// the log line to a file and line.
|
||||
func (f *Formatter) AddCallDepth(depth int) {
|
||||
f.depth += depth
|
||||
}
|
||||
105
src/server/vendor/github.com/go-logr/logr/funcr/slogsink.go
generated
vendored
Normal file
105
src/server/vendor/github.com/go-logr/logr/funcr/slogsink.go
generated
vendored
Normal file
@ -0,0 +1,105 @@
|
||||
//go:build go1.21
|
||||
// +build go1.21
|
||||
|
||||
/*
|
||||
Copyright 2023 The logr Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package funcr
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
)
|
||||
|
||||
var _ logr.SlogSink = &fnlogger{}
|
||||
|
||||
const extraSlogSinkDepth = 3 // 2 for slog, 1 for SlogSink
|
||||
|
||||
func (l fnlogger) Handle(_ context.Context, record slog.Record) error {
|
||||
kvList := make([]any, 0, 2*record.NumAttrs())
|
||||
record.Attrs(func(attr slog.Attr) bool {
|
||||
kvList = attrToKVs(attr, kvList)
|
||||
return true
|
||||
})
|
||||
|
||||
if record.Level >= slog.LevelError {
|
||||
l.WithCallDepth(extraSlogSinkDepth).Error(nil, record.Message, kvList...)
|
||||
} else {
|
||||
level := l.levelFromSlog(record.Level)
|
||||
l.WithCallDepth(extraSlogSinkDepth).Info(level, record.Message, kvList...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l fnlogger) WithAttrs(attrs []slog.Attr) logr.SlogSink {
|
||||
kvList := make([]any, 0, 2*len(attrs))
|
||||
for _, attr := range attrs {
|
||||
kvList = attrToKVs(attr, kvList)
|
||||
}
|
||||
l.AddValues(kvList)
|
||||
return &l
|
||||
}
|
||||
|
||||
func (l fnlogger) WithGroup(name string) logr.SlogSink {
|
||||
l.startGroup(name)
|
||||
return &l
|
||||
}
|
||||
|
||||
// attrToKVs appends a slog.Attr to a logr-style kvList. It handle slog Groups
|
||||
// and other details of slog.
|
||||
func attrToKVs(attr slog.Attr, kvList []any) []any {
|
||||
attrVal := attr.Value.Resolve()
|
||||
if attrVal.Kind() == slog.KindGroup {
|
||||
groupVal := attrVal.Group()
|
||||
grpKVs := make([]any, 0, 2*len(groupVal))
|
||||
for _, attr := range groupVal {
|
||||
grpKVs = attrToKVs(attr, grpKVs)
|
||||
}
|
||||
if attr.Key == "" {
|
||||
// slog says we have to inline these
|
||||
kvList = append(kvList, grpKVs...)
|
||||
} else {
|
||||
kvList = append(kvList, attr.Key, PseudoStruct(grpKVs))
|
||||
}
|
||||
} else if attr.Key != "" {
|
||||
kvList = append(kvList, attr.Key, attrVal.Any())
|
||||
}
|
||||
|
||||
return kvList
|
||||
}
|
||||
|
||||
// levelFromSlog adjusts the level by the logger's verbosity and negates it.
|
||||
// It ensures that the result is >= 0. This is necessary because the result is
|
||||
// passed to a LogSink and that API did not historically document whether
|
||||
// levels could be negative or what that meant.
|
||||
//
|
||||
// Some example usage:
|
||||
//
|
||||
// logrV0 := getMyLogger()
|
||||
// logrV2 := logrV0.V(2)
|
||||
// slogV2 := slog.New(logr.ToSlogHandler(logrV2))
|
||||
// slogV2.Debug("msg") // =~ logrV2.V(4) =~ logrV0.V(6)
|
||||
// slogV2.Info("msg") // =~ logrV2.V(0) =~ logrV0.V(2)
|
||||
// slogv2.Warn("msg") // =~ logrV2.V(-4) =~ logrV0.V(0)
|
||||
func (l fnlogger) levelFromSlog(level slog.Level) int {
|
||||
result := -level
|
||||
if result < 0 {
|
||||
result = 0 // because LogSink doesn't expect negative V levels
|
||||
}
|
||||
return int(result)
|
||||
}
|
||||
520
src/server/vendor/github.com/go-logr/logr/logr.go
generated
vendored
Normal file
520
src/server/vendor/github.com/go-logr/logr/logr.go
generated
vendored
Normal file
@ -0,0 +1,520 @@
|
||||
/*
|
||||
Copyright 2019 The logr Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// This design derives from Dave Cheney's blog:
|
||||
// http://dave.cheney.net/2015/11/05/lets-talk-about-logging
|
||||
|
||||
// Package logr defines a general-purpose logging API and abstract interfaces
|
||||
// to back that API. Packages in the Go ecosystem can depend on this package,
|
||||
// while callers can implement logging with whatever backend is appropriate.
|
||||
//
|
||||
// # Usage
|
||||
//
|
||||
// Logging is done using a Logger instance. Logger is a concrete type with
|
||||
// methods, which defers the actual logging to a LogSink interface. The main
|
||||
// methods of Logger are Info() and Error(). Arguments to Info() and Error()
|
||||
// are key/value pairs rather than printf-style formatted strings, emphasizing
|
||||
// "structured logging".
|
||||
//
|
||||
// With Go's standard log package, we might write:
|
||||
//
|
||||
// log.Printf("setting target value %s", targetValue)
|
||||
//
|
||||
// With logr's structured logging, we'd write:
|
||||
//
|
||||
// logger.Info("setting target", "value", targetValue)
|
||||
//
|
||||
// Errors are much the same. Instead of:
|
||||
//
|
||||
// log.Printf("failed to open the pod bay door for user %s: %v", user, err)
|
||||
//
|
||||
// We'd write:
|
||||
//
|
||||
// logger.Error(err, "failed to open the pod bay door", "user", user)
|
||||
//
|
||||
// Info() and Error() are very similar, but they are separate methods so that
|
||||
// LogSink implementations can choose to do things like attach additional
|
||||
// information (such as stack traces) on calls to Error(). Error() messages are
|
||||
// always logged, regardless of the current verbosity. If there is no error
|
||||
// instance available, passing nil is valid.
|
||||
//
|
||||
// # Verbosity
|
||||
//
|
||||
// Often we want to log information only when the application in "verbose
|
||||
// mode". To write log lines that are more verbose, Logger has a V() method.
|
||||
// The higher the V-level of a log line, the less critical it is considered.
|
||||
// Log-lines with V-levels that are not enabled (as per the LogSink) will not
|
||||
// be written. Level V(0) is the default, and logger.V(0).Info() has the same
|
||||
// meaning as logger.Info(). Negative V-levels have the same meaning as V(0).
|
||||
// Error messages do not have a verbosity level and are always logged.
|
||||
//
|
||||
// Where we might have written:
|
||||
//
|
||||
// if flVerbose >= 2 {
|
||||
// log.Printf("an unusual thing happened")
|
||||
// }
|
||||
//
|
||||
// We can write:
|
||||
//
|
||||
// logger.V(2).Info("an unusual thing happened")
|
||||
//
|
||||
// # Logger Names
|
||||
//
|
||||
// Logger instances can have name strings so that all messages logged through
|
||||
// that instance have additional context. For example, you might want to add
|
||||
// a subsystem name:
|
||||
//
|
||||
// logger.WithName("compactor").Info("started", "time", time.Now())
|
||||
//
|
||||
// The WithName() method returns a new Logger, which can be passed to
|
||||
// constructors or other functions for further use. Repeated use of WithName()
|
||||
// will accumulate name "segments". These name segments will be joined in some
|
||||
// way by the LogSink implementation. It is strongly recommended that name
|
||||
// segments contain simple identifiers (letters, digits, and hyphen), and do
|
||||
// not contain characters that could muddle the log output or confuse the
|
||||
// joining operation (e.g. whitespace, commas, periods, slashes, brackets,
|
||||
// quotes, etc).
|
||||
//
|
||||
// # Saved Values
|
||||
//
|
||||
// Logger instances can store any number of key/value pairs, which will be
|
||||
// logged alongside all messages logged through that instance. For example,
|
||||
// you might want to create a Logger instance per managed object:
|
||||
//
|
||||
// With the standard log package, we might write:
|
||||
//
|
||||
// log.Printf("decided to set field foo to value %q for object %s/%s",
|
||||
// targetValue, object.Namespace, object.Name)
|
||||
//
|
||||
// With logr we'd write:
|
||||
//
|
||||
// // Elsewhere: set up the logger to log the object name.
|
||||
// obj.logger = mainLogger.WithValues(
|
||||
// "name", obj.name, "namespace", obj.namespace)
|
||||
//
|
||||
// // later on...
|
||||
// obj.logger.Info("setting foo", "value", targetValue)
|
||||
//
|
||||
// # Best Practices
|
||||
//
|
||||
// Logger has very few hard rules, with the goal that LogSink implementations
|
||||
// might have a lot of freedom to differentiate. There are, however, some
|
||||
// things to consider.
|
||||
//
|
||||
// The log message consists of a constant message attached to the log line.
|
||||
// This should generally be a simple description of what's occurring, and should
|
||||
// never be a format string. Variable information can then be attached using
|
||||
// named values.
|
||||
//
|
||||
// Keys are arbitrary strings, but should generally be constant values. Values
|
||||
// may be any Go value, but how the value is formatted is determined by the
|
||||
// LogSink implementation.
|
||||
//
|
||||
// Logger instances are meant to be passed around by value. Code that receives
|
||||
// such a value can call its methods without having to check whether the
|
||||
// instance is ready for use.
|
||||
//
|
||||
// The zero logger (= Logger{}) is identical to Discard() and discards all log
|
||||
// entries. Code that receives a Logger by value can simply call it, the methods
|
||||
// will never crash. For cases where passing a logger is optional, a pointer to Logger
|
||||
// should be used.
|
||||
//
|
||||
// # Key Naming Conventions
|
||||
//
|
||||
// Keys are not strictly required to conform to any specification or regex, but
|
||||
// it is recommended that they:
|
||||
// - be human-readable and meaningful (not auto-generated or simple ordinals)
|
||||
// - be constant (not dependent on input data)
|
||||
// - contain only printable characters
|
||||
// - not contain whitespace or punctuation
|
||||
// - use lower case for simple keys and lowerCamelCase for more complex ones
|
||||
//
|
||||
// These guidelines help ensure that log data is processed properly regardless
|
||||
// of the log implementation. For example, log implementations will try to
|
||||
// output JSON data or will store data for later database (e.g. SQL) queries.
|
||||
//
|
||||
// While users are generally free to use key names of their choice, it's
|
||||
// generally best to avoid using the following keys, as they're frequently used
|
||||
// by implementations:
|
||||
// - "caller": the calling information (file/line) of a particular log line
|
||||
// - "error": the underlying error value in the `Error` method
|
||||
// - "level": the log level
|
||||
// - "logger": the name of the associated logger
|
||||
// - "msg": the log message
|
||||
// - "stacktrace": the stack trace associated with a particular log line or
|
||||
// error (often from the `Error` message)
|
||||
// - "ts": the timestamp for a log line
|
||||
//
|
||||
// Implementations are encouraged to make use of these keys to represent the
|
||||
// above concepts, when necessary (for example, in a pure-JSON output form, it
|
||||
// would be necessary to represent at least message and timestamp as ordinary
|
||||
// named values).
|
||||
//
|
||||
// # Break Glass
|
||||
//
|
||||
// Implementations may choose to give callers access to the underlying
|
||||
// logging implementation. The recommended pattern for this is:
|
||||
//
|
||||
// // Underlier exposes access to the underlying logging implementation.
|
||||
// // Since callers only have a logr.Logger, they have to know which
|
||||
// // implementation is in use, so this interface is less of an abstraction
|
||||
// // and more of way to test type conversion.
|
||||
// type Underlier interface {
|
||||
// GetUnderlying() <underlying-type>
|
||||
// }
|
||||
//
|
||||
// Logger grants access to the sink to enable type assertions like this:
|
||||
//
|
||||
// func DoSomethingWithImpl(log logr.Logger) {
|
||||
// if underlier, ok := log.GetSink().(impl.Underlier); ok {
|
||||
// implLogger := underlier.GetUnderlying()
|
||||
// ...
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Custom `With*` functions can be implemented by copying the complete
|
||||
// Logger struct and replacing the sink in the copy:
|
||||
//
|
||||
// // WithFooBar changes the foobar parameter in the log sink and returns a
|
||||
// // new logger with that modified sink. It does nothing for loggers where
|
||||
// // the sink doesn't support that parameter.
|
||||
// func WithFoobar(log logr.Logger, foobar int) logr.Logger {
|
||||
// if foobarLogSink, ok := log.GetSink().(FoobarSink); ok {
|
||||
// log = log.WithSink(foobarLogSink.WithFooBar(foobar))
|
||||
// }
|
||||
// return log
|
||||
// }
|
||||
//
|
||||
// Don't use New to construct a new Logger with a LogSink retrieved from an
|
||||
// existing Logger. Source code attribution might not work correctly and
|
||||
// unexported fields in Logger get lost.
|
||||
//
|
||||
// Beware that the same LogSink instance may be shared by different logger
|
||||
// instances. Calling functions that modify the LogSink will affect all of
|
||||
// those.
|
||||
package logr
|
||||
|
||||
// New returns a new Logger instance. This is primarily used by libraries
|
||||
// implementing LogSink, rather than end users. Passing a nil sink will create
|
||||
// a Logger which discards all log lines.
|
||||
func New(sink LogSink) Logger {
|
||||
logger := Logger{}
|
||||
logger.setSink(sink)
|
||||
if sink != nil {
|
||||
sink.Init(runtimeInfo)
|
||||
}
|
||||
return logger
|
||||
}
|
||||
|
||||
// setSink stores the sink and updates any related fields. It mutates the
|
||||
// logger and thus is only safe to use for loggers that are not currently being
|
||||
// used concurrently.
|
||||
func (l *Logger) setSink(sink LogSink) {
|
||||
l.sink = sink
|
||||
}
|
||||
|
||||
// GetSink returns the stored sink.
|
||||
func (l Logger) GetSink() LogSink {
|
||||
return l.sink
|
||||
}
|
||||
|
||||
// WithSink returns a copy of the logger with the new sink.
|
||||
func (l Logger) WithSink(sink LogSink) Logger {
|
||||
l.setSink(sink)
|
||||
return l
|
||||
}
|
||||
|
||||
// Logger is an interface to an abstract logging implementation. This is a
|
||||
// concrete type for performance reasons, but all the real work is passed on to
|
||||
// a LogSink. Implementations of LogSink should provide their own constructors
|
||||
// that return Logger, not LogSink.
|
||||
//
|
||||
// The underlying sink can be accessed through GetSink and be modified through
|
||||
// WithSink. This enables the implementation of custom extensions (see "Break
|
||||
// Glass" in the package documentation). Normally the sink should be used only
|
||||
// indirectly.
|
||||
type Logger struct {
|
||||
sink LogSink
|
||||
level int
|
||||
}
|
||||
|
||||
// Enabled tests whether this Logger is enabled. For example, commandline
|
||||
// flags might be used to set the logging verbosity and disable some info logs.
|
||||
func (l Logger) Enabled() bool {
|
||||
// Some implementations of LogSink look at the caller in Enabled (e.g.
|
||||
// different verbosity levels per package or file), but we only pass one
|
||||
// CallDepth in (via Init). This means that all calls from Logger to the
|
||||
// LogSink's Enabled, Info, and Error methods must have the same number of
|
||||
// frames. In other words, Logger methods can't call other Logger methods
|
||||
// which call these LogSink methods unless we do it the same in all paths.
|
||||
return l.sink != nil && l.sink.Enabled(l.level)
|
||||
}
|
||||
|
||||
// Info logs a non-error message with the given key/value pairs as context.
|
||||
//
|
||||
// The msg argument should be used to add some constant description to the log
|
||||
// line. The key/value pairs can then be used to add additional variable
|
||||
// information. The key/value pairs must alternate string keys and arbitrary
|
||||
// values.
|
||||
func (l Logger) Info(msg string, keysAndValues ...any) {
|
||||
if l.sink == nil {
|
||||
return
|
||||
}
|
||||
if l.sink.Enabled(l.level) { // see comment in Enabled
|
||||
if withHelper, ok := l.sink.(CallStackHelperLogSink); ok {
|
||||
withHelper.GetCallStackHelper()()
|
||||
}
|
||||
l.sink.Info(l.level, msg, keysAndValues...)
|
||||
}
|
||||
}
|
||||
|
||||
// Error logs an error, with the given message and key/value pairs as context.
|
||||
// It functions similarly to Info, but may have unique behavior, and should be
|
||||
// preferred for logging errors (see the package documentations for more
|
||||
// information). The log message will always be emitted, regardless of
|
||||
// verbosity level.
|
||||
//
|
||||
// The msg argument should be used to add context to any underlying error,
|
||||
// while the err argument should be used to attach the actual error that
|
||||
// triggered this log line, if present. The err parameter is optional
|
||||
// and nil may be passed instead of an error instance.
|
||||
func (l Logger) Error(err error, msg string, keysAndValues ...any) {
|
||||
if l.sink == nil {
|
||||
return
|
||||
}
|
||||
if withHelper, ok := l.sink.(CallStackHelperLogSink); ok {
|
||||
withHelper.GetCallStackHelper()()
|
||||
}
|
||||
l.sink.Error(err, msg, keysAndValues...)
|
||||
}
|
||||
|
||||
// V returns a new Logger instance for a specific verbosity level, relative to
|
||||
// this Logger. In other words, V-levels are additive. A higher verbosity
|
||||
// level means a log message is less important. Negative V-levels are treated
|
||||
// as 0.
|
||||
func (l Logger) V(level int) Logger {
|
||||
if l.sink == nil {
|
||||
return l
|
||||
}
|
||||
if level < 0 {
|
||||
level = 0
|
||||
}
|
||||
l.level += level
|
||||
return l
|
||||
}
|
||||
|
||||
// GetV returns the verbosity level of the logger. If the logger's LogSink is
|
||||
// nil as in the Discard logger, this will always return 0.
|
||||
func (l Logger) GetV() int {
|
||||
// 0 if l.sink nil because of the if check in V above.
|
||||
return l.level
|
||||
}
|
||||
|
||||
// WithValues returns a new Logger instance with additional key/value pairs.
|
||||
// See Info for documentation on how key/value pairs work.
|
||||
func (l Logger) WithValues(keysAndValues ...any) Logger {
|
||||
if l.sink == nil {
|
||||
return l
|
||||
}
|
||||
l.setSink(l.sink.WithValues(keysAndValues...))
|
||||
return l
|
||||
}
|
||||
|
||||
// WithName returns a new Logger instance with the specified name element added
|
||||
// to the Logger's name. Successive calls with WithName append additional
|
||||
// suffixes to the Logger's name. It's strongly recommended that name segments
|
||||
// contain only letters, digits, and hyphens (see the package documentation for
|
||||
// more information).
|
||||
func (l Logger) WithName(name string) Logger {
|
||||
if l.sink == nil {
|
||||
return l
|
||||
}
|
||||
l.setSink(l.sink.WithName(name))
|
||||
return l
|
||||
}
|
||||
|
||||
// WithCallDepth returns a Logger instance that offsets the call stack by the
|
||||
// specified number of frames when logging call site information, if possible.
|
||||
// This is useful for users who have helper functions between the "real" call
|
||||
// site and the actual calls to Logger methods. If depth is 0 the attribution
|
||||
// should be to the direct caller of this function. If depth is 1 the
|
||||
// attribution should skip 1 call frame, and so on. Successive calls to this
|
||||
// are additive.
|
||||
//
|
||||
// If the underlying log implementation supports a WithCallDepth(int) method,
|
||||
// it will be called and the result returned. If the implementation does not
|
||||
// support CallDepthLogSink, the original Logger will be returned.
|
||||
//
|
||||
// To skip one level, WithCallStackHelper() should be used instead of
|
||||
// WithCallDepth(1) because it works with implementions that support the
|
||||
// CallDepthLogSink and/or CallStackHelperLogSink interfaces.
|
||||
func (l Logger) WithCallDepth(depth int) Logger {
|
||||
if l.sink == nil {
|
||||
return l
|
||||
}
|
||||
if withCallDepth, ok := l.sink.(CallDepthLogSink); ok {
|
||||
l.setSink(withCallDepth.WithCallDepth(depth))
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
// WithCallStackHelper returns a new Logger instance that skips the direct
|
||||
// caller when logging call site information, if possible. This is useful for
|
||||
// users who have helper functions between the "real" call site and the actual
|
||||
// calls to Logger methods and want to support loggers which depend on marking
|
||||
// each individual helper function, like loggers based on testing.T.
|
||||
//
|
||||
// In addition to using that new logger instance, callers also must call the
|
||||
// returned function.
|
||||
//
|
||||
// If the underlying log implementation supports a WithCallDepth(int) method,
|
||||
// WithCallDepth(1) will be called to produce a new logger. If it supports a
|
||||
// WithCallStackHelper() method, that will be also called. If the
|
||||
// implementation does not support either of these, the original Logger will be
|
||||
// returned.
|
||||
func (l Logger) WithCallStackHelper() (func(), Logger) {
|
||||
if l.sink == nil {
|
||||
return func() {}, l
|
||||
}
|
||||
var helper func()
|
||||
if withCallDepth, ok := l.sink.(CallDepthLogSink); ok {
|
||||
l.setSink(withCallDepth.WithCallDepth(1))
|
||||
}
|
||||
if withHelper, ok := l.sink.(CallStackHelperLogSink); ok {
|
||||
helper = withHelper.GetCallStackHelper()
|
||||
} else {
|
||||
helper = func() {}
|
||||
}
|
||||
return helper, l
|
||||
}
|
||||
|
||||
// IsZero returns true if this logger is an uninitialized zero value
|
||||
func (l Logger) IsZero() bool {
|
||||
return l.sink == nil
|
||||
}
|
||||
|
||||
// RuntimeInfo holds information that the logr "core" library knows which
|
||||
// LogSinks might want to know.
|
||||
type RuntimeInfo struct {
|
||||
// CallDepth is the number of call frames the logr library adds between the
|
||||
// end-user and the LogSink. LogSink implementations which choose to print
|
||||
// the original logging site (e.g. file & line) should climb this many
|
||||
// additional frames to find it.
|
||||
CallDepth int
|
||||
}
|
||||
|
||||
// runtimeInfo is a static global. It must not be changed at run time.
|
||||
var runtimeInfo = RuntimeInfo{
|
||||
CallDepth: 1,
|
||||
}
|
||||
|
||||
// LogSink represents a logging implementation. End-users will generally not
|
||||
// interact with this type.
|
||||
type LogSink interface {
|
||||
// Init receives optional information about the logr library for LogSink
|
||||
// implementations that need it.
|
||||
Init(info RuntimeInfo)
|
||||
|
||||
// Enabled tests whether this LogSink is enabled at the specified V-level.
|
||||
// For example, commandline flags might be used to set the logging
|
||||
// verbosity and disable some info logs.
|
||||
Enabled(level int) bool
|
||||
|
||||
// Info logs a non-error message with the given key/value pairs as context.
|
||||
// The level argument is provided for optional logging. This method will
|
||||
// only be called when Enabled(level) is true. See Logger.Info for more
|
||||
// details.
|
||||
Info(level int, msg string, keysAndValues ...any)
|
||||
|
||||
// Error logs an error, with the given message and key/value pairs as
|
||||
// context. See Logger.Error for more details.
|
||||
Error(err error, msg string, keysAndValues ...any)
|
||||
|
||||
// WithValues returns a new LogSink with additional key/value pairs. See
|
||||
// Logger.WithValues for more details.
|
||||
WithValues(keysAndValues ...any) LogSink
|
||||
|
||||
// WithName returns a new LogSink with the specified name appended. See
|
||||
// Logger.WithName for more details.
|
||||
WithName(name string) LogSink
|
||||
}
|
||||
|
||||
// CallDepthLogSink represents a LogSink that knows how to climb the call stack
|
||||
// to identify the original call site and can offset the depth by a specified
|
||||
// number of frames. This is useful for users who have helper functions
|
||||
// between the "real" call site and the actual calls to Logger methods.
|
||||
// Implementations that log information about the call site (such as file,
|
||||
// function, or line) would otherwise log information about the intermediate
|
||||
// helper functions.
|
||||
//
|
||||
// This is an optional interface and implementations are not required to
|
||||
// support it.
|
||||
type CallDepthLogSink interface {
|
||||
// WithCallDepth returns a LogSink that will offset the call
|
||||
// stack by the specified number of frames when logging call
|
||||
// site information.
|
||||
//
|
||||
// If depth is 0, the LogSink should skip exactly the number
|
||||
// of call frames defined in RuntimeInfo.CallDepth when Info
|
||||
// or Error are called, i.e. the attribution should be to the
|
||||
// direct caller of Logger.Info or Logger.Error.
|
||||
//
|
||||
// If depth is 1 the attribution should skip 1 call frame, and so on.
|
||||
// Successive calls to this are additive.
|
||||
WithCallDepth(depth int) LogSink
|
||||
}
|
||||
|
||||
// CallStackHelperLogSink represents a LogSink that knows how to climb
|
||||
// the call stack to identify the original call site and can skip
|
||||
// intermediate helper functions if they mark themselves as
|
||||
// helper. Go's testing package uses that approach.
|
||||
//
|
||||
// This is useful for users who have helper functions between the
|
||||
// "real" call site and the actual calls to Logger methods.
|
||||
// Implementations that log information about the call site (such as
|
||||
// file, function, or line) would otherwise log information about the
|
||||
// intermediate helper functions.
|
||||
//
|
||||
// This is an optional interface and implementations are not required
|
||||
// to support it. Implementations that choose to support this must not
|
||||
// simply implement it as WithCallDepth(1), because
|
||||
// Logger.WithCallStackHelper will call both methods if they are
|
||||
// present. This should only be implemented for LogSinks that actually
|
||||
// need it, as with testing.T.
|
||||
type CallStackHelperLogSink interface {
|
||||
// GetCallStackHelper returns a function that must be called
|
||||
// to mark the direct caller as helper function when logging
|
||||
// call site information.
|
||||
GetCallStackHelper() func()
|
||||
}
|
||||
|
||||
// Marshaler is an optional interface that logged values may choose to
|
||||
// implement. Loggers with structured output, such as JSON, should
|
||||
// log the object return by the MarshalLog method instead of the
|
||||
// original value.
|
||||
type Marshaler interface {
|
||||
// MarshalLog can be used to:
|
||||
// - ensure that structs are not logged as strings when the original
|
||||
// value has a String method: return a different type without a
|
||||
// String method
|
||||
// - select which fields of a complex type should get logged:
|
||||
// return a simpler struct with fewer fields
|
||||
// - log unexported fields: return a different struct
|
||||
// with exported fields
|
||||
//
|
||||
// It may return any value of any type.
|
||||
MarshalLog() any
|
||||
}
|
||||
192
src/server/vendor/github.com/go-logr/logr/sloghandler.go
generated
vendored
Normal file
192
src/server/vendor/github.com/go-logr/logr/sloghandler.go
generated
vendored
Normal file
@ -0,0 +1,192 @@
|
||||
//go:build go1.21
|
||||
// +build go1.21
|
||||
|
||||
/*
|
||||
Copyright 2023 The logr Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package logr
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
type slogHandler struct {
|
||||
// May be nil, in which case all logs get discarded.
|
||||
sink LogSink
|
||||
// Non-nil if sink is non-nil and implements SlogSink.
|
||||
slogSink SlogSink
|
||||
|
||||
// groupPrefix collects values from WithGroup calls. It gets added as
|
||||
// prefix to value keys when handling a log record.
|
||||
groupPrefix string
|
||||
|
||||
// levelBias can be set when constructing the handler to influence the
|
||||
// slog.Level of log records. A positive levelBias reduces the
|
||||
// slog.Level value. slog has no API to influence this value after the
|
||||
// handler got created, so it can only be set indirectly through
|
||||
// Logger.V.
|
||||
levelBias slog.Level
|
||||
}
|
||||
|
||||
var _ slog.Handler = &slogHandler{}
|
||||
|
||||
// groupSeparator is used to concatenate WithGroup names and attribute keys.
|
||||
const groupSeparator = "."
|
||||
|
||||
// GetLevel is used for black box unit testing.
|
||||
func (l *slogHandler) GetLevel() slog.Level {
|
||||
return l.levelBias
|
||||
}
|
||||
|
||||
func (l *slogHandler) Enabled(_ context.Context, level slog.Level) bool {
|
||||
return l.sink != nil && (level >= slog.LevelError || l.sink.Enabled(l.levelFromSlog(level)))
|
||||
}
|
||||
|
||||
func (l *slogHandler) Handle(ctx context.Context, record slog.Record) error {
|
||||
if l.slogSink != nil {
|
||||
// Only adjust verbosity level of log entries < slog.LevelError.
|
||||
if record.Level < slog.LevelError {
|
||||
record.Level -= l.levelBias
|
||||
}
|
||||
return l.slogSink.Handle(ctx, record)
|
||||
}
|
||||
|
||||
// No need to check for nil sink here because Handle will only be called
|
||||
// when Enabled returned true.
|
||||
|
||||
kvList := make([]any, 0, 2*record.NumAttrs())
|
||||
record.Attrs(func(attr slog.Attr) bool {
|
||||
kvList = attrToKVs(attr, l.groupPrefix, kvList)
|
||||
return true
|
||||
})
|
||||
if record.Level >= slog.LevelError {
|
||||
l.sinkWithCallDepth().Error(nil, record.Message, kvList...)
|
||||
} else {
|
||||
level := l.levelFromSlog(record.Level)
|
||||
l.sinkWithCallDepth().Info(level, record.Message, kvList...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// sinkWithCallDepth adjusts the stack unwinding so that when Error or Info
|
||||
// are called by Handle, code in slog gets skipped.
|
||||
//
|
||||
// This offset currently (Go 1.21.0) works for calls through
|
||||
// slog.New(ToSlogHandler(...)). There's no guarantee that the call
|
||||
// chain won't change. Wrapping the handler will also break unwinding. It's
|
||||
// still better than not adjusting at all....
|
||||
//
|
||||
// This cannot be done when constructing the handler because FromSlogHandler needs
|
||||
// access to the original sink without this adjustment. A second copy would
|
||||
// work, but then WithAttrs would have to be called for both of them.
|
||||
func (l *slogHandler) sinkWithCallDepth() LogSink {
|
||||
if sink, ok := l.sink.(CallDepthLogSink); ok {
|
||||
return sink.WithCallDepth(2)
|
||||
}
|
||||
return l.sink
|
||||
}
|
||||
|
||||
func (l *slogHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
||||
if l.sink == nil || len(attrs) == 0 {
|
||||
return l
|
||||
}
|
||||
|
||||
clone := *l
|
||||
if l.slogSink != nil {
|
||||
clone.slogSink = l.slogSink.WithAttrs(attrs)
|
||||
clone.sink = clone.slogSink
|
||||
} else {
|
||||
kvList := make([]any, 0, 2*len(attrs))
|
||||
for _, attr := range attrs {
|
||||
kvList = attrToKVs(attr, l.groupPrefix, kvList)
|
||||
}
|
||||
clone.sink = l.sink.WithValues(kvList...)
|
||||
}
|
||||
return &clone
|
||||
}
|
||||
|
||||
func (l *slogHandler) WithGroup(name string) slog.Handler {
|
||||
if l.sink == nil {
|
||||
return l
|
||||
}
|
||||
if name == "" {
|
||||
// slog says to inline empty groups
|
||||
return l
|
||||
}
|
||||
clone := *l
|
||||
if l.slogSink != nil {
|
||||
clone.slogSink = l.slogSink.WithGroup(name)
|
||||
clone.sink = clone.slogSink
|
||||
} else {
|
||||
clone.groupPrefix = addPrefix(clone.groupPrefix, name)
|
||||
}
|
||||
return &clone
|
||||
}
|
||||
|
||||
// attrToKVs appends a slog.Attr to a logr-style kvList. It handle slog Groups
|
||||
// and other details of slog.
|
||||
func attrToKVs(attr slog.Attr, groupPrefix string, kvList []any) []any {
|
||||
attrVal := attr.Value.Resolve()
|
||||
if attrVal.Kind() == slog.KindGroup {
|
||||
groupVal := attrVal.Group()
|
||||
grpKVs := make([]any, 0, 2*len(groupVal))
|
||||
prefix := groupPrefix
|
||||
if attr.Key != "" {
|
||||
prefix = addPrefix(groupPrefix, attr.Key)
|
||||
}
|
||||
for _, attr := range groupVal {
|
||||
grpKVs = attrToKVs(attr, prefix, grpKVs)
|
||||
}
|
||||
kvList = append(kvList, grpKVs...)
|
||||
} else if attr.Key != "" {
|
||||
kvList = append(kvList, addPrefix(groupPrefix, attr.Key), attrVal.Any())
|
||||
}
|
||||
|
||||
return kvList
|
||||
}
|
||||
|
||||
func addPrefix(prefix, name string) string {
|
||||
if prefix == "" {
|
||||
return name
|
||||
}
|
||||
if name == "" {
|
||||
return prefix
|
||||
}
|
||||
return prefix + groupSeparator + name
|
||||
}
|
||||
|
||||
// levelFromSlog adjusts the level by the logger's verbosity and negates it.
|
||||
// It ensures that the result is >= 0. This is necessary because the result is
|
||||
// passed to a LogSink and that API did not historically document whether
|
||||
// levels could be negative or what that meant.
|
||||
//
|
||||
// Some example usage:
|
||||
//
|
||||
// logrV0 := getMyLogger()
|
||||
// logrV2 := logrV0.V(2)
|
||||
// slogV2 := slog.New(logr.ToSlogHandler(logrV2))
|
||||
// slogV2.Debug("msg") // =~ logrV2.V(4) =~ logrV0.V(6)
|
||||
// slogV2.Info("msg") // =~ logrV2.V(0) =~ logrV0.V(2)
|
||||
// slogv2.Warn("msg") // =~ logrV2.V(-4) =~ logrV0.V(0)
|
||||
func (l *slogHandler) levelFromSlog(level slog.Level) int {
|
||||
result := -level
|
||||
result += l.levelBias // in case the original Logger had a V level
|
||||
if result < 0 {
|
||||
result = 0 // because LogSink doesn't expect negative V levels
|
||||
}
|
||||
return int(result)
|
||||
}
|
||||
100
src/server/vendor/github.com/go-logr/logr/slogr.go
generated
vendored
Normal file
100
src/server/vendor/github.com/go-logr/logr/slogr.go
generated
vendored
Normal file
@ -0,0 +1,100 @@
|
||||
//go:build go1.21
|
||||
// +build go1.21
|
||||
|
||||
/*
|
||||
Copyright 2023 The logr Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package logr
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
// FromSlogHandler returns a Logger which writes to the slog.Handler.
|
||||
//
|
||||
// The logr verbosity level is mapped to slog levels such that V(0) becomes
|
||||
// slog.LevelInfo and V(4) becomes slog.LevelDebug.
|
||||
func FromSlogHandler(handler slog.Handler) Logger {
|
||||
if handler, ok := handler.(*slogHandler); ok {
|
||||
if handler.sink == nil {
|
||||
return Discard()
|
||||
}
|
||||
return New(handler.sink).V(int(handler.levelBias))
|
||||
}
|
||||
return New(&slogSink{handler: handler})
|
||||
}
|
||||
|
||||
// ToSlogHandler returns a slog.Handler which writes to the same sink as the Logger.
|
||||
//
|
||||
// The returned logger writes all records with level >= slog.LevelError as
|
||||
// error log entries with LogSink.Error, regardless of the verbosity level of
|
||||
// the Logger:
|
||||
//
|
||||
// logger := <some Logger with 0 as verbosity level>
|
||||
// slog.New(ToSlogHandler(logger.V(10))).Error(...) -> logSink.Error(...)
|
||||
//
|
||||
// The level of all other records gets reduced by the verbosity
|
||||
// level of the Logger and the result is negated. If it happens
|
||||
// to be negative, then it gets replaced by zero because a LogSink
|
||||
// is not expected to handled negative levels:
|
||||
//
|
||||
// slog.New(ToSlogHandler(logger)).Debug(...) -> logger.GetSink().Info(level=4, ...)
|
||||
// slog.New(ToSlogHandler(logger)).Warning(...) -> logger.GetSink().Info(level=0, ...)
|
||||
// slog.New(ToSlogHandler(logger)).Info(...) -> logger.GetSink().Info(level=0, ...)
|
||||
// slog.New(ToSlogHandler(logger.V(4))).Info(...) -> logger.GetSink().Info(level=4, ...)
|
||||
func ToSlogHandler(logger Logger) slog.Handler {
|
||||
if sink, ok := logger.GetSink().(*slogSink); ok && logger.GetV() == 0 {
|
||||
return sink.handler
|
||||
}
|
||||
|
||||
handler := &slogHandler{sink: logger.GetSink(), levelBias: slog.Level(logger.GetV())}
|
||||
if slogSink, ok := handler.sink.(SlogSink); ok {
|
||||
handler.slogSink = slogSink
|
||||
}
|
||||
return handler
|
||||
}
|
||||
|
||||
// SlogSink is an optional interface that a LogSink can implement to support
|
||||
// logging through the slog.Logger or slog.Handler APIs better. It then should
|
||||
// also support special slog values like slog.Group. When used as a
|
||||
// slog.Handler, the advantages are:
|
||||
//
|
||||
// - stack unwinding gets avoided in favor of logging the pre-recorded PC,
|
||||
// as intended by slog
|
||||
// - proper grouping of key/value pairs via WithGroup
|
||||
// - verbosity levels > slog.LevelInfo can be recorded
|
||||
// - less overhead
|
||||
//
|
||||
// Both APIs (Logger and slog.Logger/Handler) then are supported equally
|
||||
// well. Developers can pick whatever API suits them better and/or mix
|
||||
// packages which use either API in the same binary with a common logging
|
||||
// implementation.
|
||||
//
|
||||
// This interface is necessary because the type implementing the LogSink
|
||||
// interface cannot also implement the slog.Handler interface due to the
|
||||
// different prototype of the common Enabled method.
|
||||
//
|
||||
// An implementation could support both interfaces in two different types, but then
|
||||
// additional interfaces would be needed to convert between those types in FromSlogHandler
|
||||
// and ToSlogHandler.
|
||||
type SlogSink interface {
|
||||
LogSink
|
||||
|
||||
Handle(ctx context.Context, record slog.Record) error
|
||||
WithAttrs(attrs []slog.Attr) SlogSink
|
||||
WithGroup(name string) SlogSink
|
||||
}
|
||||
120
src/server/vendor/github.com/go-logr/logr/slogsink.go
generated
vendored
Normal file
120
src/server/vendor/github.com/go-logr/logr/slogsink.go
generated
vendored
Normal file
@ -0,0 +1,120 @@
|
||||
//go:build go1.21
|
||||
// +build go1.21
|
||||
|
||||
/*
|
||||
Copyright 2023 The logr Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package logr
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
_ LogSink = &slogSink{}
|
||||
_ CallDepthLogSink = &slogSink{}
|
||||
_ Underlier = &slogSink{}
|
||||
)
|
||||
|
||||
// Underlier is implemented by the LogSink returned by NewFromLogHandler.
|
||||
type Underlier interface {
|
||||
// GetUnderlying returns the Handler used by the LogSink.
|
||||
GetUnderlying() slog.Handler
|
||||
}
|
||||
|
||||
const (
|
||||
// nameKey is used to log the `WithName` values as an additional attribute.
|
||||
nameKey = "logger"
|
||||
|
||||
// errKey is used to log the error parameter of Error as an additional attribute.
|
||||
errKey = "err"
|
||||
)
|
||||
|
||||
type slogSink struct {
|
||||
callDepth int
|
||||
name string
|
||||
handler slog.Handler
|
||||
}
|
||||
|
||||
func (l *slogSink) Init(info RuntimeInfo) {
|
||||
l.callDepth = info.CallDepth
|
||||
}
|
||||
|
||||
func (l *slogSink) GetUnderlying() slog.Handler {
|
||||
return l.handler
|
||||
}
|
||||
|
||||
func (l *slogSink) WithCallDepth(depth int) LogSink {
|
||||
newLogger := *l
|
||||
newLogger.callDepth += depth
|
||||
return &newLogger
|
||||
}
|
||||
|
||||
func (l *slogSink) Enabled(level int) bool {
|
||||
return l.handler.Enabled(context.Background(), slog.Level(-level))
|
||||
}
|
||||
|
||||
func (l *slogSink) Info(level int, msg string, kvList ...interface{}) {
|
||||
l.log(nil, msg, slog.Level(-level), kvList...)
|
||||
}
|
||||
|
||||
func (l *slogSink) Error(err error, msg string, kvList ...interface{}) {
|
||||
l.log(err, msg, slog.LevelError, kvList...)
|
||||
}
|
||||
|
||||
func (l *slogSink) log(err error, msg string, level slog.Level, kvList ...interface{}) {
|
||||
var pcs [1]uintptr
|
||||
// skip runtime.Callers, this function, Info/Error, and all helper functions above that.
|
||||
runtime.Callers(3+l.callDepth, pcs[:])
|
||||
|
||||
record := slog.NewRecord(time.Now(), level, msg, pcs[0])
|
||||
if l.name != "" {
|
||||
record.AddAttrs(slog.String(nameKey, l.name))
|
||||
}
|
||||
if err != nil {
|
||||
record.AddAttrs(slog.Any(errKey, err))
|
||||
}
|
||||
record.Add(kvList...)
|
||||
_ = l.handler.Handle(context.Background(), record)
|
||||
}
|
||||
|
||||
func (l slogSink) WithName(name string) LogSink {
|
||||
if l.name != "" {
|
||||
l.name += "/"
|
||||
}
|
||||
l.name += name
|
||||
return &l
|
||||
}
|
||||
|
||||
func (l slogSink) WithValues(kvList ...interface{}) LogSink {
|
||||
l.handler = l.handler.WithAttrs(kvListToAttrs(kvList...))
|
||||
return &l
|
||||
}
|
||||
|
||||
func kvListToAttrs(kvList ...interface{}) []slog.Attr {
|
||||
// We don't need the record itself, only its Add method.
|
||||
record := slog.NewRecord(time.Time{}, 0, "", 0)
|
||||
record.Add(kvList...)
|
||||
attrs := make([]slog.Attr, 0, record.NumAttrs())
|
||||
record.Attrs(func(attr slog.Attr) bool {
|
||||
attrs = append(attrs, attr)
|
||||
return true
|
||||
})
|
||||
return attrs
|
||||
}
|
||||
201
src/server/vendor/github.com/go-logr/stdr/LICENSE
generated
vendored
Normal file
201
src/server/vendor/github.com/go-logr/stdr/LICENSE
generated
vendored
Normal file
@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
6
src/server/vendor/github.com/go-logr/stdr/README.md
generated
vendored
Normal file
6
src/server/vendor/github.com/go-logr/stdr/README.md
generated
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
# Minimal Go logging using logr and Go's standard library
|
||||
|
||||
[](https://pkg.go.dev/github.com/go-logr/stdr)
|
||||
|
||||
This package implements the [logr interface](https://github.com/go-logr/logr)
|
||||
in terms of Go's standard log package(https://pkg.go.dev/log).
|
||||
170
src/server/vendor/github.com/go-logr/stdr/stdr.go
generated
vendored
Normal file
170
src/server/vendor/github.com/go-logr/stdr/stdr.go
generated
vendored
Normal file
@ -0,0 +1,170 @@
|
||||
/*
|
||||
Copyright 2019 The logr Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package stdr implements github.com/go-logr/logr.Logger in terms of
|
||||
// Go's standard log package.
|
||||
package stdr
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/go-logr/logr/funcr"
|
||||
)
|
||||
|
||||
// The global verbosity level. See SetVerbosity().
|
||||
var globalVerbosity int
|
||||
|
||||
// SetVerbosity sets the global level against which all info logs will be
|
||||
// compared. If this is greater than or equal to the "V" of the logger, the
|
||||
// message will be logged. A higher value here means more logs will be written.
|
||||
// The previous verbosity value is returned. This is not concurrent-safe -
|
||||
// callers must be sure to call it from only one goroutine.
|
||||
func SetVerbosity(v int) int {
|
||||
old := globalVerbosity
|
||||
globalVerbosity = v
|
||||
return old
|
||||
}
|
||||
|
||||
// New returns a logr.Logger which is implemented by Go's standard log package,
|
||||
// or something like it. If std is nil, this will use a default logger
|
||||
// instead.
|
||||
//
|
||||
// Example: stdr.New(log.New(os.Stderr, "", log.LstdFlags|log.Lshortfile)))
|
||||
func New(std StdLogger) logr.Logger {
|
||||
return NewWithOptions(std, Options{})
|
||||
}
|
||||
|
||||
// NewWithOptions returns a logr.Logger which is implemented by Go's standard
|
||||
// log package, or something like it. See New for details.
|
||||
func NewWithOptions(std StdLogger, opts Options) logr.Logger {
|
||||
if std == nil {
|
||||
// Go's log.Default() is only available in 1.16 and higher.
|
||||
std = log.New(os.Stderr, "", log.LstdFlags)
|
||||
}
|
||||
|
||||
if opts.Depth < 0 {
|
||||
opts.Depth = 0
|
||||
}
|
||||
|
||||
fopts := funcr.Options{
|
||||
LogCaller: funcr.MessageClass(opts.LogCaller),
|
||||
}
|
||||
|
||||
sl := &logger{
|
||||
Formatter: funcr.NewFormatter(fopts),
|
||||
std: std,
|
||||
}
|
||||
|
||||
// For skipping our own logger.Info/Error.
|
||||
sl.Formatter.AddCallDepth(1 + opts.Depth)
|
||||
|
||||
return logr.New(sl)
|
||||
}
|
||||
|
||||
// Options carries parameters which influence the way logs are generated.
|
||||
type Options struct {
|
||||
// Depth biases the assumed number of call frames to the "true" caller.
|
||||
// This is useful when the calling code calls a function which then calls
|
||||
// stdr (e.g. a logging shim to another API). Values less than zero will
|
||||
// be treated as zero.
|
||||
Depth int
|
||||
|
||||
// LogCaller tells stdr to add a "caller" key to some or all log lines.
|
||||
// Go's log package has options to log this natively, too.
|
||||
LogCaller MessageClass
|
||||
|
||||
// TODO: add an option to log the date/time
|
||||
}
|
||||
|
||||
// MessageClass indicates which category or categories of messages to consider.
|
||||
type MessageClass int
|
||||
|
||||
const (
|
||||
// None ignores all message classes.
|
||||
None MessageClass = iota
|
||||
// All considers all message classes.
|
||||
All
|
||||
// Info only considers info messages.
|
||||
Info
|
||||
// Error only considers error messages.
|
||||
Error
|
||||
)
|
||||
|
||||
// StdLogger is the subset of the Go stdlib log.Logger API that is needed for
|
||||
// this adapter.
|
||||
type StdLogger interface {
|
||||
// Output is the same as log.Output and log.Logger.Output.
|
||||
Output(calldepth int, logline string) error
|
||||
}
|
||||
|
||||
type logger struct {
|
||||
funcr.Formatter
|
||||
std StdLogger
|
||||
}
|
||||
|
||||
var _ logr.LogSink = &logger{}
|
||||
var _ logr.CallDepthLogSink = &logger{}
|
||||
|
||||
func (l logger) Enabled(level int) bool {
|
||||
return globalVerbosity >= level
|
||||
}
|
||||
|
||||
func (l logger) Info(level int, msg string, kvList ...interface{}) {
|
||||
prefix, args := l.FormatInfo(level, msg, kvList)
|
||||
if prefix != "" {
|
||||
args = prefix + ": " + args
|
||||
}
|
||||
_ = l.std.Output(l.Formatter.GetDepth()+1, args)
|
||||
}
|
||||
|
||||
func (l logger) Error(err error, msg string, kvList ...interface{}) {
|
||||
prefix, args := l.FormatError(err, msg, kvList)
|
||||
if prefix != "" {
|
||||
args = prefix + ": " + args
|
||||
}
|
||||
_ = l.std.Output(l.Formatter.GetDepth()+1, args)
|
||||
}
|
||||
|
||||
func (l logger) WithName(name string) logr.LogSink {
|
||||
l.Formatter.AddName(name)
|
||||
return &l
|
||||
}
|
||||
|
||||
func (l logger) WithValues(kvList ...interface{}) logr.LogSink {
|
||||
l.Formatter.AddValues(kvList)
|
||||
return &l
|
||||
}
|
||||
|
||||
func (l logger) WithCallDepth(depth int) logr.LogSink {
|
||||
l.Formatter.AddCallDepth(depth)
|
||||
return &l
|
||||
}
|
||||
|
||||
// Underlier exposes access to the underlying logging implementation. Since
|
||||
// callers only have a logr.Logger, they have to know which implementation is
|
||||
// in use, so this interface is less of an abstraction and more of way to test
|
||||
// type conversion.
|
||||
type Underlier interface {
|
||||
GetUnderlying() StdLogger
|
||||
}
|
||||
|
||||
// GetUnderlying returns the StdLogger underneath this logger. Since StdLogger
|
||||
// is itself an interface, the result may or may not be a Go log.Logger.
|
||||
func (l logger) GetUnderlying() StdLogger {
|
||||
return l.std
|
||||
}
|
||||
6
src/server/vendor/github.com/google/s2a-go/.gitignore
generated
vendored
Normal file
6
src/server/vendor/github.com/google/s2a-go/.gitignore
generated
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
# Ignore binaries without extension
|
||||
//example/client/client
|
||||
//example/server/server
|
||||
//internal/v2/fakes2av2_server/fakes2av2_server
|
||||
|
||||
.idea/
|
||||
93
src/server/vendor/github.com/google/s2a-go/CODE_OF_CONDUCT.md
generated
vendored
Normal file
93
src/server/vendor/github.com/google/s2a-go/CODE_OF_CONDUCT.md
generated
vendored
Normal file
@ -0,0 +1,93 @@
|
||||
# Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, gender identity and expression, level of
|
||||
experience, education, socio-economic status, nationality, personal appearance,
|
||||
race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, or to ban temporarily or permanently any
|
||||
contributor for other behaviors that they deem inappropriate, threatening,
|
||||
offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
This Code of Conduct also applies outside the project spaces when the Project
|
||||
Steward has a reasonable belief that an individual's behavior may have a
|
||||
negative impact on the project or its community.
|
||||
|
||||
## Conflict Resolution
|
||||
|
||||
We do not believe that all conflict is bad; healthy debate and disagreement
|
||||
often yield positive results. However, it is never okay to be disrespectful or
|
||||
to engage in behavior that violates the project’s code of conduct.
|
||||
|
||||
If you see someone violating the code of conduct, you are encouraged to address
|
||||
the behavior directly with those involved. Many issues can be resolved quickly
|
||||
and easily, and this gives people more control over the outcome of their
|
||||
dispute. If you are unable to resolve the matter for any reason, or if the
|
||||
behavior is threatening or harassing, report it. We are dedicated to providing
|
||||
an environment where participants feel welcome and safe.
|
||||
|
||||
Reports should be directed to *[PROJECT STEWARD NAME(s) AND EMAIL(s)]*, the
|
||||
Project Steward(s) for *[PROJECT NAME]*. It is the Project Steward’s duty to
|
||||
receive and address reported violations of the code of conduct. They will then
|
||||
work with a committee consisting of representatives from the Open Source
|
||||
Programs Office and the Google Open Source Strategy team. If for any reason you
|
||||
are uncomfortable reaching out to the Project Steward, please email
|
||||
opensource@google.com.
|
||||
|
||||
We will investigate every complaint, but you may not receive a direct response.
|
||||
We will use our discretion in determining when and how to follow up on reported
|
||||
incidents, which may range from not taking action to permanent expulsion from
|
||||
the project and project-sponsored spaces. We will notify the accused of the
|
||||
report and provide them an opportunity to discuss it before any action is taken.
|
||||
The identity of the reporter will be omitted from the details of the report
|
||||
supplied to the accused. In potentially harmful situations, such as ongoing
|
||||
harassment or threats to anyone's safety, we may take action without notice.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the Contributor Covenant, version 1.4,
|
||||
available at
|
||||
https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
29
src/server/vendor/github.com/google/s2a-go/CONTRIBUTING.md
generated
vendored
Normal file
29
src/server/vendor/github.com/google/s2a-go/CONTRIBUTING.md
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
# How to Contribute
|
||||
|
||||
We'd love to accept your patches and contributions to this project. There are
|
||||
just a few small guidelines you need to follow.
|
||||
|
||||
## Contributor License Agreement
|
||||
|
||||
Contributions to this project must be accompanied by a Contributor License
|
||||
Agreement (CLA). You (or your employer) retain the copyright to your
|
||||
contribution; this simply gives us permission to use and redistribute your
|
||||
contributions as part of the project. Head over to
|
||||
<https://cla.developers.google.com/> to see your current agreements on file or
|
||||
to sign a new one.
|
||||
|
||||
You generally only need to submit a CLA once, so if you've already submitted one
|
||||
(even if it was for a different project), you probably don't need to do it
|
||||
again.
|
||||
|
||||
## Code reviews
|
||||
|
||||
All submissions, including submissions by project members, require review. We
|
||||
use GitHub pull requests for this purpose. Consult
|
||||
[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
|
||||
information on using pull requests.
|
||||
|
||||
## Community Guidelines
|
||||
|
||||
This project follows
|
||||
[Google's Open Source Community Guidelines](https://opensource.google/conduct/).
|
||||
202
src/server/vendor/github.com/google/s2a-go/LICENSE.md
generated
vendored
Normal file
202
src/server/vendor/github.com/google/s2a-go/LICENSE.md
generated
vendored
Normal file
@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
14
src/server/vendor/github.com/google/s2a-go/README.md
generated
vendored
Normal file
14
src/server/vendor/github.com/google/s2a-go/README.md
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
# Secure Session Agent Client Libraries
|
||||
|
||||
The Secure Session Agent is a service that enables a workload to offload select
|
||||
operations from the mTLS handshake and protects a workload's private key
|
||||
material from exfiltration. Specifically, the workload asks the Secure Session
|
||||
Agent for the TLS configuration to use during the handshake, to perform private
|
||||
key operations, and to validate the peer certificate chain. The Secure Session
|
||||
Agent's client libraries enable applications to communicate with the Secure
|
||||
Session Agent during the TLS handshake, and to encrypt traffic to the peer
|
||||
after the TLS handshake is complete.
|
||||
|
||||
This repository contains the source code for the Secure Session Agent's Go
|
||||
client libraries, which allow gRPC and HTTP Go applications to use the Secure Session
|
||||
Agent.
|
||||
167
src/server/vendor/github.com/google/s2a-go/fallback/s2a_fallback.go
generated
vendored
Normal file
167
src/server/vendor/github.com/google/s2a-go/fallback/s2a_fallback.go
generated
vendored
Normal file
@ -0,0 +1,167 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2023 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
// Package fallback provides default implementations of fallback options when S2A fails.
|
||||
package fallback
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
)
|
||||
|
||||
const (
|
||||
alpnProtoStrH2 = "h2"
|
||||
alpnProtoStrHTTP = "http/1.1"
|
||||
defaultHTTPSPort = "443"
|
||||
)
|
||||
|
||||
// FallbackTLSConfigGRPC is a tls.Config used by the DefaultFallbackClientHandshakeFunc function.
|
||||
// It supports GRPC use case, thus the alpn is set to 'h2'.
|
||||
var FallbackTLSConfigGRPC = tls.Config{
|
||||
MinVersion: tls.VersionTLS13,
|
||||
ClientSessionCache: nil,
|
||||
NextProtos: []string{alpnProtoStrH2},
|
||||
}
|
||||
|
||||
// FallbackTLSConfigHTTP is a tls.Config used by the DefaultFallbackDialerAndAddress func.
|
||||
// It supports the HTTP use case and the alpn is set to both 'http/1.1' and 'h2'.
|
||||
var FallbackTLSConfigHTTP = tls.Config{
|
||||
MinVersion: tls.VersionTLS13,
|
||||
ClientSessionCache: nil,
|
||||
NextProtos: []string{alpnProtoStrH2, alpnProtoStrHTTP},
|
||||
}
|
||||
|
||||
// ClientHandshake establishes a TLS connection and returns it, plus its auth info.
|
||||
// Inputs:
|
||||
//
|
||||
// targetServer: the server attempted with S2A.
|
||||
// conn: the tcp connection to the server at address targetServer that was passed into S2A's ClientHandshake func.
|
||||
// If fallback is successful, the `conn` should be closed.
|
||||
// err: the error encountered when performing the client-side TLS handshake with S2A.
|
||||
type ClientHandshake func(ctx context.Context, targetServer string, conn net.Conn, err error) (net.Conn, credentials.AuthInfo, error)
|
||||
|
||||
// DefaultFallbackClientHandshakeFunc returns a ClientHandshake function,
|
||||
// which establishes a TLS connection to the provided fallbackAddr, returns the new connection and its auth info.
|
||||
// Example use:
|
||||
//
|
||||
// transportCreds, _ = s2a.NewClientCreds(&s2a.ClientOptions{
|
||||
// S2AAddress: s2aAddress,
|
||||
// FallbackOpts: &s2a.FallbackOptions{ // optional
|
||||
// FallbackClientHandshakeFunc: fallback.DefaultFallbackClientHandshakeFunc(fallbackAddr),
|
||||
// },
|
||||
// })
|
||||
//
|
||||
// The fallback server's certificate must be verifiable using OS root store.
|
||||
// The fallbackAddr is expected to be a network address, e.g. example.com:port. If port is not specified,
|
||||
// it uses default port 443.
|
||||
// In the returned function's TLS config, ClientSessionCache is explicitly set to nil to disable TLS resumption,
|
||||
// and min TLS version is set to 1.3.
|
||||
func DefaultFallbackClientHandshakeFunc(fallbackAddr string) (ClientHandshake, error) {
|
||||
var fallbackDialer = tls.Dialer{Config: &FallbackTLSConfigGRPC}
|
||||
return defaultFallbackClientHandshakeFuncInternal(fallbackAddr, fallbackDialer.DialContext)
|
||||
}
|
||||
|
||||
func defaultFallbackClientHandshakeFuncInternal(fallbackAddr string, dialContextFunc func(context.Context, string, string) (net.Conn, error)) (ClientHandshake, error) {
|
||||
fallbackServerAddr, err := processFallbackAddr(fallbackAddr)
|
||||
if err != nil {
|
||||
if grpclog.V(1) {
|
||||
grpclog.Infof("error processing fallback address [%s]: %v", fallbackAddr, err)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return func(ctx context.Context, targetServer string, conn net.Conn, s2aErr error) (net.Conn, credentials.AuthInfo, error) {
|
||||
fbConn, fbErr := dialContextFunc(ctx, "tcp", fallbackServerAddr)
|
||||
if fbErr != nil {
|
||||
grpclog.Infof("dialing to fallback server %s failed: %v", fallbackServerAddr, fbErr)
|
||||
return nil, nil, fmt.Errorf("dialing to fallback server %s failed: %v; S2A client handshake with %s error: %w", fallbackServerAddr, fbErr, targetServer, s2aErr)
|
||||
}
|
||||
|
||||
tc, success := fbConn.(*tls.Conn)
|
||||
if !success {
|
||||
grpclog.Infof("the connection with fallback server is expected to be tls but isn't")
|
||||
return nil, nil, fmt.Errorf("the connection with fallback server is expected to be tls but isn't; S2A client handshake with %s error: %w", targetServer, s2aErr)
|
||||
}
|
||||
|
||||
tlsInfo := credentials.TLSInfo{
|
||||
State: tc.ConnectionState(),
|
||||
CommonAuthInfo: credentials.CommonAuthInfo{
|
||||
SecurityLevel: credentials.PrivacyAndIntegrity,
|
||||
},
|
||||
}
|
||||
if grpclog.V(1) {
|
||||
grpclog.Infof("ConnectionState.NegotiatedProtocol: %v", tc.ConnectionState().NegotiatedProtocol)
|
||||
grpclog.Infof("ConnectionState.HandshakeComplete: %v", tc.ConnectionState().HandshakeComplete)
|
||||
grpclog.Infof("ConnectionState.ServerName: %v", tc.ConnectionState().ServerName)
|
||||
}
|
||||
conn.Close()
|
||||
return fbConn, tlsInfo, nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DefaultFallbackDialerAndAddress returns a TLS dialer and the network address to dial.
|
||||
// Example use:
|
||||
//
|
||||
// fallbackDialer, fallbackServerAddr := fallback.DefaultFallbackDialerAndAddress(fallbackAddr)
|
||||
// dialTLSContext := s2a.NewS2aDialTLSContextFunc(&s2a.ClientOptions{
|
||||
// S2AAddress: s2aAddress, // required
|
||||
// FallbackOpts: &s2a.FallbackOptions{
|
||||
// FallbackDialer: &s2a.FallbackDialer{
|
||||
// Dialer: fallbackDialer,
|
||||
// ServerAddr: fallbackServerAddr,
|
||||
// },
|
||||
// },
|
||||
// })
|
||||
//
|
||||
// The fallback server's certificate should be verifiable using OS root store.
|
||||
// The fallbackAddr is expected to be a network address, e.g. example.com:port. If port is not specified,
|
||||
// it uses default port 443.
|
||||
// In the returned function's TLS config, ClientSessionCache is explicitly set to nil to disable TLS resumption,
|
||||
// and min TLS version is set to 1.3.
|
||||
func DefaultFallbackDialerAndAddress(fallbackAddr string) (*tls.Dialer, string, error) {
|
||||
fallbackServerAddr, err := processFallbackAddr(fallbackAddr)
|
||||
if err != nil {
|
||||
if grpclog.V(1) {
|
||||
grpclog.Infof("error processing fallback address [%s]: %v", fallbackAddr, err)
|
||||
}
|
||||
return nil, "", err
|
||||
}
|
||||
return &tls.Dialer{Config: &FallbackTLSConfigHTTP}, fallbackServerAddr, nil
|
||||
}
|
||||
|
||||
func processFallbackAddr(fallbackAddr string) (string, error) {
|
||||
var fallbackServerAddr string
|
||||
var err error
|
||||
|
||||
if fallbackAddr == "" {
|
||||
return "", fmt.Errorf("empty fallback address")
|
||||
}
|
||||
_, _, err = net.SplitHostPort(fallbackAddr)
|
||||
if err != nil {
|
||||
// fallbackAddr does not have port suffix
|
||||
fallbackServerAddr = net.JoinHostPort(fallbackAddr, defaultHTTPSPort)
|
||||
} else {
|
||||
// FallbackServerAddr already has port suffix
|
||||
fallbackServerAddr = fallbackAddr
|
||||
}
|
||||
return fallbackServerAddr, nil
|
||||
}
|
||||
119
src/server/vendor/github.com/google/s2a-go/internal/authinfo/authinfo.go
generated
vendored
Normal file
119
src/server/vendor/github.com/google/s2a-go/internal/authinfo/authinfo.go
generated
vendored
Normal file
@ -0,0 +1,119 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
// Package authinfo provides authentication and authorization information that
|
||||
// results from the TLS handshake.
|
||||
package authinfo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
commonpb "github.com/google/s2a-go/internal/proto/common_go_proto"
|
||||
contextpb "github.com/google/s2a-go/internal/proto/s2a_context_go_proto"
|
||||
grpcpb "github.com/google/s2a-go/internal/proto/s2a_go_proto"
|
||||
"google.golang.org/grpc/credentials"
|
||||
)
|
||||
|
||||
var _ credentials.AuthInfo = (*S2AAuthInfo)(nil)
|
||||
|
||||
const s2aAuthType = "s2a"
|
||||
|
||||
// S2AAuthInfo exposes authentication and authorization information from the
|
||||
// S2A session result to the gRPC stack.
|
||||
type S2AAuthInfo struct {
|
||||
s2aContext *contextpb.S2AContext
|
||||
commonAuthInfo credentials.CommonAuthInfo
|
||||
}
|
||||
|
||||
// NewS2AAuthInfo returns a new S2AAuthInfo object from the S2A session result.
|
||||
func NewS2AAuthInfo(result *grpcpb.SessionResult) (credentials.AuthInfo, error) {
|
||||
return newS2AAuthInfo(result)
|
||||
}
|
||||
|
||||
func newS2AAuthInfo(result *grpcpb.SessionResult) (*S2AAuthInfo, error) {
|
||||
if result == nil {
|
||||
return nil, errors.New("NewS2aAuthInfo given nil session result")
|
||||
}
|
||||
return &S2AAuthInfo{
|
||||
s2aContext: &contextpb.S2AContext{
|
||||
ApplicationProtocol: result.GetApplicationProtocol(),
|
||||
TlsVersion: result.GetState().GetTlsVersion(),
|
||||
Ciphersuite: result.GetState().GetTlsCiphersuite(),
|
||||
PeerIdentity: result.GetPeerIdentity(),
|
||||
LocalIdentity: result.GetLocalIdentity(),
|
||||
PeerCertFingerprint: result.GetPeerCertFingerprint(),
|
||||
LocalCertFingerprint: result.GetLocalCertFingerprint(),
|
||||
IsHandshakeResumed: result.GetState().GetIsHandshakeResumed(),
|
||||
},
|
||||
commonAuthInfo: credentials.CommonAuthInfo{SecurityLevel: credentials.PrivacyAndIntegrity},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// AuthType returns the authentication type.
|
||||
func (s *S2AAuthInfo) AuthType() string {
|
||||
return s2aAuthType
|
||||
}
|
||||
|
||||
// ApplicationProtocol returns the application protocol, e.g. "grpc".
|
||||
func (s *S2AAuthInfo) ApplicationProtocol() string {
|
||||
return s.s2aContext.GetApplicationProtocol()
|
||||
}
|
||||
|
||||
// TLSVersion returns the TLS version negotiated during the handshake.
|
||||
func (s *S2AAuthInfo) TLSVersion() commonpb.TLSVersion {
|
||||
return s.s2aContext.GetTlsVersion()
|
||||
}
|
||||
|
||||
// Ciphersuite returns the ciphersuite negotiated during the handshake.
|
||||
func (s *S2AAuthInfo) Ciphersuite() commonpb.Ciphersuite {
|
||||
return s.s2aContext.GetCiphersuite()
|
||||
}
|
||||
|
||||
// PeerIdentity returns the authenticated identity of the peer.
|
||||
func (s *S2AAuthInfo) PeerIdentity() *commonpb.Identity {
|
||||
return s.s2aContext.GetPeerIdentity()
|
||||
}
|
||||
|
||||
// LocalIdentity returns the local identity of the application used during
|
||||
// session setup.
|
||||
func (s *S2AAuthInfo) LocalIdentity() *commonpb.Identity {
|
||||
return s.s2aContext.GetLocalIdentity()
|
||||
}
|
||||
|
||||
// PeerCertFingerprint returns the SHA256 hash of the peer certificate used in
|
||||
// the S2A handshake.
|
||||
func (s *S2AAuthInfo) PeerCertFingerprint() []byte {
|
||||
return s.s2aContext.GetPeerCertFingerprint()
|
||||
}
|
||||
|
||||
// LocalCertFingerprint returns the SHA256 hash of the local certificate used
|
||||
// in the S2A handshake.
|
||||
func (s *S2AAuthInfo) LocalCertFingerprint() []byte {
|
||||
return s.s2aContext.GetLocalCertFingerprint()
|
||||
}
|
||||
|
||||
// IsHandshakeResumed returns true if a cached session was used to resume
|
||||
// the handshake.
|
||||
func (s *S2AAuthInfo) IsHandshakeResumed() bool {
|
||||
return s.s2aContext.GetIsHandshakeResumed()
|
||||
}
|
||||
|
||||
// SecurityLevel returns the security level of the connection.
|
||||
func (s *S2AAuthInfo) SecurityLevel() credentials.SecurityLevel {
|
||||
return s.commonAuthInfo.SecurityLevel
|
||||
}
|
||||
438
src/server/vendor/github.com/google/s2a-go/internal/handshaker/handshaker.go
generated
vendored
Normal file
438
src/server/vendor/github.com/google/s2a-go/internal/handshaker/handshaker.go
generated
vendored
Normal file
@ -0,0 +1,438 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
// Package handshaker communicates with the S2A handshaker service.
|
||||
package handshaker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/google/s2a-go/internal/authinfo"
|
||||
commonpb "github.com/google/s2a-go/internal/proto/common_go_proto"
|
||||
s2apb "github.com/google/s2a-go/internal/proto/s2a_go_proto"
|
||||
"github.com/google/s2a-go/internal/record"
|
||||
"github.com/google/s2a-go/internal/tokenmanager"
|
||||
grpc "google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
)
|
||||
|
||||
var (
|
||||
// appProtocol contains the application protocol accepted by the handshaker.
|
||||
appProtocol = "grpc"
|
||||
// frameLimit is the maximum size of a frame in bytes.
|
||||
frameLimit = 1024 * 64
|
||||
// peerNotRespondingError is the error thrown when the peer doesn't respond.
|
||||
errPeerNotResponding = errors.New("peer is not responding and re-connection should be attempted")
|
||||
)
|
||||
|
||||
// Handshaker defines a handshaker interface.
|
||||
type Handshaker interface {
|
||||
// ClientHandshake starts and completes a TLS handshake from the client side,
|
||||
// and returns a secure connection along with additional auth information.
|
||||
ClientHandshake(ctx context.Context) (net.Conn, credentials.AuthInfo, error)
|
||||
// ServerHandshake starts and completes a TLS handshake from the server side,
|
||||
// and returns a secure connection along with additional auth information.
|
||||
ServerHandshake(ctx context.Context) (net.Conn, credentials.AuthInfo, error)
|
||||
// Close terminates the Handshaker. It should be called when the handshake
|
||||
// is complete.
|
||||
Close() error
|
||||
}
|
||||
|
||||
// ClientHandshakerOptions contains the options needed to configure the S2A
|
||||
// handshaker service on the client-side.
|
||||
type ClientHandshakerOptions struct {
|
||||
// MinTLSVersion specifies the min TLS version supported by the client.
|
||||
MinTLSVersion commonpb.TLSVersion
|
||||
// MaxTLSVersion specifies the max TLS version supported by the client.
|
||||
MaxTLSVersion commonpb.TLSVersion
|
||||
// TLSCiphersuites is the ordered list of ciphersuites supported by the
|
||||
// client.
|
||||
TLSCiphersuites []commonpb.Ciphersuite
|
||||
// TargetIdentities contains a list of allowed server identities. One of the
|
||||
// target identities should match the peer identity in the handshake
|
||||
// result; otherwise, the handshake fails.
|
||||
TargetIdentities []*commonpb.Identity
|
||||
// LocalIdentity is the local identity of the client application. If none is
|
||||
// provided, then the S2A will choose the default identity.
|
||||
LocalIdentity *commonpb.Identity
|
||||
// TargetName is the allowed server name, which may be used for server
|
||||
// authorization check by the S2A if it is provided.
|
||||
TargetName string
|
||||
// EnsureProcessSessionTickets allows users to wait and ensure that all
|
||||
// available session tickets are sent to S2A before a process completes.
|
||||
EnsureProcessSessionTickets *sync.WaitGroup
|
||||
}
|
||||
|
||||
// ServerHandshakerOptions contains the options needed to configure the S2A
|
||||
// handshaker service on the server-side.
|
||||
type ServerHandshakerOptions struct {
|
||||
// MinTLSVersion specifies the min TLS version supported by the server.
|
||||
MinTLSVersion commonpb.TLSVersion
|
||||
// MaxTLSVersion specifies the max TLS version supported by the server.
|
||||
MaxTLSVersion commonpb.TLSVersion
|
||||
// TLSCiphersuites is the ordered list of ciphersuites supported by the
|
||||
// server.
|
||||
TLSCiphersuites []commonpb.Ciphersuite
|
||||
// LocalIdentities is the list of local identities that may be assumed by
|
||||
// the server. If no local identity is specified, then the S2A chooses a
|
||||
// default local identity.
|
||||
LocalIdentities []*commonpb.Identity
|
||||
}
|
||||
|
||||
// s2aHandshaker performs a TLS handshake using the S2A handshaker service.
|
||||
type s2aHandshaker struct {
|
||||
// stream is used to communicate with the S2A handshaker service.
|
||||
stream s2apb.S2AService_SetUpSessionClient
|
||||
// conn is the connection to the peer.
|
||||
conn net.Conn
|
||||
// clientOpts should be non-nil iff the handshaker is client-side.
|
||||
clientOpts *ClientHandshakerOptions
|
||||
// serverOpts should be non-nil iff the handshaker is server-side.
|
||||
serverOpts *ServerHandshakerOptions
|
||||
// isClient determines if the handshaker is client or server side.
|
||||
isClient bool
|
||||
// hsAddr stores the address of the S2A handshaker service.
|
||||
hsAddr string
|
||||
// tokenManager manages access tokens for authenticating to S2A.
|
||||
tokenManager tokenmanager.AccessTokenManager
|
||||
// localIdentities is the set of local identities for whom the
|
||||
// tokenManager should fetch a token when preparing a request to be
|
||||
// sent to S2A.
|
||||
localIdentities []*commonpb.Identity
|
||||
}
|
||||
|
||||
// NewClientHandshaker creates an s2aHandshaker instance that performs a
|
||||
// client-side TLS handshake using the S2A handshaker service.
|
||||
func NewClientHandshaker(ctx context.Context, conn *grpc.ClientConn, c net.Conn, hsAddr string, opts *ClientHandshakerOptions) (Handshaker, error) {
|
||||
stream, err := s2apb.NewS2AServiceClient(conn).SetUpSession(ctx, grpc.WaitForReady(true))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tokenManager, err := tokenmanager.NewSingleTokenAccessTokenManager()
|
||||
if err != nil {
|
||||
grpclog.Infof("failed to create single token access token manager: %v", err)
|
||||
}
|
||||
return newClientHandshaker(stream, c, hsAddr, opts, tokenManager), nil
|
||||
}
|
||||
|
||||
func newClientHandshaker(stream s2apb.S2AService_SetUpSessionClient, c net.Conn, hsAddr string, opts *ClientHandshakerOptions, tokenManager tokenmanager.AccessTokenManager) *s2aHandshaker {
|
||||
var localIdentities []*commonpb.Identity
|
||||
if opts != nil {
|
||||
localIdentities = []*commonpb.Identity{opts.LocalIdentity}
|
||||
}
|
||||
return &s2aHandshaker{
|
||||
stream: stream,
|
||||
conn: c,
|
||||
clientOpts: opts,
|
||||
isClient: true,
|
||||
hsAddr: hsAddr,
|
||||
tokenManager: tokenManager,
|
||||
localIdentities: localIdentities,
|
||||
}
|
||||
}
|
||||
|
||||
// NewServerHandshaker creates an s2aHandshaker instance that performs a
|
||||
// server-side TLS handshake using the S2A handshaker service.
|
||||
func NewServerHandshaker(ctx context.Context, conn *grpc.ClientConn, c net.Conn, hsAddr string, opts *ServerHandshakerOptions) (Handshaker, error) {
|
||||
stream, err := s2apb.NewS2AServiceClient(conn).SetUpSession(ctx, grpc.WaitForReady(true))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tokenManager, err := tokenmanager.NewSingleTokenAccessTokenManager()
|
||||
if err != nil {
|
||||
grpclog.Infof("failed to create single token access token manager: %v", err)
|
||||
}
|
||||
return newServerHandshaker(stream, c, hsAddr, opts, tokenManager), nil
|
||||
}
|
||||
|
||||
func newServerHandshaker(stream s2apb.S2AService_SetUpSessionClient, c net.Conn, hsAddr string, opts *ServerHandshakerOptions, tokenManager tokenmanager.AccessTokenManager) *s2aHandshaker {
|
||||
var localIdentities []*commonpb.Identity
|
||||
if opts != nil {
|
||||
localIdentities = opts.LocalIdentities
|
||||
}
|
||||
return &s2aHandshaker{
|
||||
stream: stream,
|
||||
conn: c,
|
||||
serverOpts: opts,
|
||||
isClient: false,
|
||||
hsAddr: hsAddr,
|
||||
tokenManager: tokenManager,
|
||||
localIdentities: localIdentities,
|
||||
}
|
||||
}
|
||||
|
||||
// ClientHandshake performs a client-side TLS handshake using the S2A handshaker
|
||||
// service. When complete, returns a TLS connection.
|
||||
func (h *s2aHandshaker) ClientHandshake(_ context.Context) (net.Conn, credentials.AuthInfo, error) {
|
||||
if !h.isClient {
|
||||
return nil, nil, errors.New("only handshakers created using NewClientHandshaker can perform a client-side handshake")
|
||||
}
|
||||
// Extract the hostname from the target name. The target name is assumed to be an authority.
|
||||
hostname, _, err := net.SplitHostPort(h.clientOpts.TargetName)
|
||||
if err != nil {
|
||||
// If the target name had no host port or could not be parsed, use it as is.
|
||||
hostname = h.clientOpts.TargetName
|
||||
}
|
||||
|
||||
// Prepare a client start message to send to the S2A handshaker service.
|
||||
req := &s2apb.SessionReq{
|
||||
ReqOneof: &s2apb.SessionReq_ClientStart{
|
||||
ClientStart: &s2apb.ClientSessionStartReq{
|
||||
ApplicationProtocols: []string{appProtocol},
|
||||
MinTlsVersion: h.clientOpts.MinTLSVersion,
|
||||
MaxTlsVersion: h.clientOpts.MaxTLSVersion,
|
||||
TlsCiphersuites: h.clientOpts.TLSCiphersuites,
|
||||
TargetIdentities: h.clientOpts.TargetIdentities,
|
||||
LocalIdentity: h.clientOpts.LocalIdentity,
|
||||
TargetName: hostname,
|
||||
},
|
||||
},
|
||||
AuthMechanisms: h.getAuthMechanisms(),
|
||||
}
|
||||
conn, result, err := h.setUpSession(req)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
authInfo, err := authinfo.NewS2AAuthInfo(result)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return conn, authInfo, nil
|
||||
}
|
||||
|
||||
// ServerHandshake performs a server-side TLS handshake using the S2A handshaker
|
||||
// service. When complete, returns a TLS connection.
|
||||
func (h *s2aHandshaker) ServerHandshake(_ context.Context) (net.Conn, credentials.AuthInfo, error) {
|
||||
if h.isClient {
|
||||
return nil, nil, errors.New("only handshakers created using NewServerHandshaker can perform a server-side handshake")
|
||||
}
|
||||
p := make([]byte, frameLimit)
|
||||
n, err := h.conn.Read(p)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
// Prepare a server start message to send to the S2A handshaker service.
|
||||
req := &s2apb.SessionReq{
|
||||
ReqOneof: &s2apb.SessionReq_ServerStart{
|
||||
ServerStart: &s2apb.ServerSessionStartReq{
|
||||
ApplicationProtocols: []string{appProtocol},
|
||||
MinTlsVersion: h.serverOpts.MinTLSVersion,
|
||||
MaxTlsVersion: h.serverOpts.MaxTLSVersion,
|
||||
TlsCiphersuites: h.serverOpts.TLSCiphersuites,
|
||||
LocalIdentities: h.serverOpts.LocalIdentities,
|
||||
InBytes: p[:n],
|
||||
},
|
||||
},
|
||||
AuthMechanisms: h.getAuthMechanisms(),
|
||||
}
|
||||
conn, result, err := h.setUpSession(req)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
authInfo, err := authinfo.NewS2AAuthInfo(result)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return conn, authInfo, nil
|
||||
}
|
||||
|
||||
// setUpSession proxies messages between the peer and the S2A handshaker
|
||||
// service.
|
||||
func (h *s2aHandshaker) setUpSession(req *s2apb.SessionReq) (net.Conn, *s2apb.SessionResult, error) {
|
||||
resp, err := h.accessHandshakerService(req)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
// Check if the returned status is an error.
|
||||
if resp.GetStatus() != nil {
|
||||
if got, want := resp.GetStatus().Code, uint32(codes.OK); got != want {
|
||||
return nil, nil, fmt.Errorf("%v", resp.GetStatus().Details)
|
||||
}
|
||||
}
|
||||
// Calculate the extra unread bytes from the Session. Attempting to consume
|
||||
// more than the bytes sent will throw an error.
|
||||
var extra []byte
|
||||
if req.GetServerStart() != nil {
|
||||
if resp.GetBytesConsumed() > uint32(len(req.GetServerStart().GetInBytes())) {
|
||||
return nil, nil, errors.New("handshaker service consumed bytes value is out-of-bounds")
|
||||
}
|
||||
extra = req.GetServerStart().GetInBytes()[resp.GetBytesConsumed():]
|
||||
}
|
||||
result, extra, err := h.processUntilDone(resp, extra)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if result.GetLocalIdentity() == nil {
|
||||
return nil, nil, errors.New("local identity must be populated in session result")
|
||||
}
|
||||
|
||||
// Create a new TLS record protocol using the Session Result.
|
||||
newConn, err := record.NewConn(&record.ConnParameters{
|
||||
NetConn: h.conn,
|
||||
Ciphersuite: result.GetState().GetTlsCiphersuite(),
|
||||
TLSVersion: result.GetState().GetTlsVersion(),
|
||||
InTrafficSecret: result.GetState().GetInKey(),
|
||||
OutTrafficSecret: result.GetState().GetOutKey(),
|
||||
UnusedBuf: extra,
|
||||
InSequence: result.GetState().GetInSequence(),
|
||||
OutSequence: result.GetState().GetOutSequence(),
|
||||
HSAddr: h.hsAddr,
|
||||
ConnectionID: result.GetState().GetConnectionId(),
|
||||
LocalIdentity: result.GetLocalIdentity(),
|
||||
EnsureProcessSessionTickets: h.ensureProcessSessionTickets(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return newConn, result, nil
|
||||
}
|
||||
|
||||
func (h *s2aHandshaker) ensureProcessSessionTickets() *sync.WaitGroup {
|
||||
if h.clientOpts == nil {
|
||||
return nil
|
||||
}
|
||||
return h.clientOpts.EnsureProcessSessionTickets
|
||||
}
|
||||
|
||||
// accessHandshakerService sends the session request to the S2A handshaker
|
||||
// service and returns the session response.
|
||||
func (h *s2aHandshaker) accessHandshakerService(req *s2apb.SessionReq) (*s2apb.SessionResp, error) {
|
||||
if err := h.stream.Send(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := h.stream.Recv()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// processUntilDone continues proxying messages between the peer and the S2A
|
||||
// handshaker service until the handshaker service returns the SessionResult at
|
||||
// the end of the handshake or an error occurs.
|
||||
func (h *s2aHandshaker) processUntilDone(resp *s2apb.SessionResp, unusedBytes []byte) (*s2apb.SessionResult, []byte, error) {
|
||||
for {
|
||||
if len(resp.OutFrames) > 0 {
|
||||
if _, err := h.conn.Write(resp.OutFrames); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
if resp.Result != nil {
|
||||
return resp.Result, unusedBytes, nil
|
||||
}
|
||||
buf := make([]byte, frameLimit)
|
||||
n, err := h.conn.Read(buf)
|
||||
if err != nil && err != io.EOF {
|
||||
return nil, nil, err
|
||||
}
|
||||
// If there is nothing to send to the handshaker service and nothing is
|
||||
// received from the peer, then we are stuck. This covers the case when
|
||||
// the peer is not responding. Note that handshaker service connection
|
||||
// issues are caught in accessHandshakerService before we even get
|
||||
// here.
|
||||
if len(resp.OutFrames) == 0 && n == 0 {
|
||||
return nil, nil, errPeerNotResponding
|
||||
}
|
||||
// Append extra bytes from the previous interaction with the handshaker
|
||||
// service with the current buffer read from conn.
|
||||
p := append(unusedBytes, buf[:n]...)
|
||||
// From here on, p and unusedBytes point to the same slice.
|
||||
resp, err = h.accessHandshakerService(&s2apb.SessionReq{
|
||||
ReqOneof: &s2apb.SessionReq_Next{
|
||||
Next: &s2apb.SessionNextReq{
|
||||
InBytes: p,
|
||||
},
|
||||
},
|
||||
AuthMechanisms: h.getAuthMechanisms(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Cache the local identity returned by S2A, if it is populated. This
|
||||
// overwrites any existing local identities. This is done because, once the
|
||||
// S2A has selected a local identity, then only that local identity should
|
||||
// be asserted in future requests until the end of the current handshake.
|
||||
if resp.GetLocalIdentity() != nil {
|
||||
h.localIdentities = []*commonpb.Identity{resp.GetLocalIdentity()}
|
||||
}
|
||||
|
||||
// Set unusedBytes based on the handshaker service response.
|
||||
if resp.GetBytesConsumed() > uint32(len(p)) {
|
||||
return nil, nil, errors.New("handshaker service consumed bytes value is out-of-bounds")
|
||||
}
|
||||
unusedBytes = p[resp.GetBytesConsumed():]
|
||||
}
|
||||
}
|
||||
|
||||
// Close shuts down the handshaker and the stream to the S2A handshaker service
|
||||
// when the handshake is complete. It should be called when the caller obtains
|
||||
// the secure connection at the end of the handshake.
|
||||
func (h *s2aHandshaker) Close() error {
|
||||
return h.stream.CloseSend()
|
||||
}
|
||||
|
||||
func (h *s2aHandshaker) getAuthMechanisms() []*s2apb.AuthenticationMechanism {
|
||||
if h.tokenManager == nil {
|
||||
return nil
|
||||
}
|
||||
// First handle the special case when no local identities have been provided
|
||||
// by the application. In this case, an AuthenticationMechanism with no local
|
||||
// identity will be sent.
|
||||
if len(h.localIdentities) == 0 {
|
||||
token, err := h.tokenManager.DefaultToken()
|
||||
if err != nil {
|
||||
grpclog.Infof("unable to get token for empty local identity: %v", err)
|
||||
return nil
|
||||
}
|
||||
return []*s2apb.AuthenticationMechanism{
|
||||
{
|
||||
MechanismOneof: &s2apb.AuthenticationMechanism_Token{
|
||||
Token: token,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Next, handle the case where the application (or the S2A) has provided
|
||||
// one or more local identities.
|
||||
var authMechanisms []*s2apb.AuthenticationMechanism
|
||||
for _, localIdentity := range h.localIdentities {
|
||||
token, err := h.tokenManager.Token(localIdentity)
|
||||
if err != nil {
|
||||
grpclog.Infof("unable to get token for local identity %v: %v", localIdentity, err)
|
||||
continue
|
||||
}
|
||||
|
||||
authMechanism := &s2apb.AuthenticationMechanism{
|
||||
Identity: localIdentity,
|
||||
MechanismOneof: &s2apb.AuthenticationMechanism_Token{
|
||||
Token: token,
|
||||
},
|
||||
}
|
||||
authMechanisms = append(authMechanisms, authMechanism)
|
||||
}
|
||||
return authMechanisms
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user