From ca53cd6b19c3f4d22fa3074292996f0f21524ac7 Mon Sep 17 00:00:00 2001 From: Abirdcfly Date: Fri, 5 May 2023 21:55:07 +0800 Subject: [PATCH] feat: add oidc auth Signed-off-by: Abirdcfly --- cmd/bc-cli/main.go | 68 +++++++++++++ go.mod | 13 ++- go.sum | 17 ++++ pkg/auth/auth.go | 226 ++++++++++++++++++++++++++++++++++++++++++ pkg/common/config.go | 65 ++++++++++++ pkg/depository/get.go | 5 +- pkg/utils/http/get.go | 3 + 7 files changed, 391 insertions(+), 6 deletions(-) create mode 100644 pkg/auth/auth.go create mode 100644 pkg/common/config.go diff --git a/cmd/bc-cli/main.go b/cmd/bc-cli/main.go index c14c71a..d6a46ae 100644 --- a/cmd/bc-cli/main.go +++ b/cmd/bc-cli/main.go @@ -17,10 +17,19 @@ limitations under the License. package main import ( + "bytes" + "encoding/json" + goflags "flag" + "path" + "github.com/bestchains/bc-cli/cmd/bc-cli/create" delcmd "github.com/bestchains/bc-cli/cmd/bc-cli/delete" "github.com/bestchains/bc-cli/cmd/bc-cli/get" + "github.com/bestchains/bc-cli/pkg/auth" + "github.com/bestchains/bc-cli/pkg/common" "github.com/spf13/cobra" + "github.com/spf13/viper" + "k8s.io/klog/v2" ) func NewCmd() *cobra.Command { @@ -32,6 +41,45 @@ func NewCmd() *cobra.Command { return nil }, } + fs := goflags.NewFlagSet("", goflags.PanicOnError) + klog.InitFlags(fs) + cmd.PersistentFlags().AddGoFlagSet(fs) + cmd.PersistentFlags().String("issuer-url", "https://portal.172.22.96.209.nip.io/oidc", "issuer url for oidc") + cmd.PersistentFlags().Bool("enable-auth", false, "enable oidc auth") + ConfigFileFullPath := cmd.PersistentFlags().String("config", common.ConfigFilePath, "config file") + _ = viper.BindPFlag("auth.issuerurl", cmd.PersistentFlags().Lookup("issuer-url")) + _ = viper.BindPFlag("auth.enable", cmd.PersistentFlags().Lookup("enable-auth")) + + var config *common.Config + cmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) (err error) { + config, err = loadConfig(*ConfigFileFullPath) + if err != nil { + return err + } + configGet, err := auth.Auth(cmd.Context(), &config.Auth) + if err != nil { + return err + } + config.Auth = *configGet + return nil + } + + cmd.PersistentPostRunE = func(cmd *cobra.Command, args []string) (err error) { + configByte, err := json.Marshal(config) + if err != nil { + return err + } + if err := viper.ReadConfig(bytes.NewReader(configByte)); err != nil { + return err + } + if err := viper.WriteConfig(); err != nil { + if _, ok := err.(viper.ConfigFileNotFoundError); ok { + return viper.SafeWriteConfig() + } + return err + } + return nil + } cmd.AddCommand(create.NewCreateCmd()) cmd.AddCommand(get.NewGetCmd()) @@ -44,3 +92,23 @@ func main() { panic(err) } } + +func loadConfig(configFile string) (config *common.Config, err error) { + config = &common.Config{} + viper.AddConfigPath(path.Dir(configFile)) + viper.SetConfigName(path.Base(configFile)) + viper.SetConfigType(common.ConfigFileType) + err = viper.ReadInConfig() + if err != nil { + if _, ok := err.(viper.ConfigFileNotFoundError); !ok { + // ignore config file not exist error + return nil, err + } + } + err = viper.Unmarshal(config) + if err != nil { + return nil, err + } + klog.V(3).Infof("all config: %+v", config) + return config, nil +} diff --git a/go.mod b/go.mod index 337c92b..4167cc5 100644 --- a/go.mod +++ b/go.mod @@ -4,9 +4,16 @@ go 1.20 require ( github.com/bestchains/bestchains-contracts v0.0.0-20230427091538-99ca7569cde7 + github.com/coreos/go-oidc/v3 v3.5.0 github.com/golangci/golangci-lint v1.52.2 + github.com/int128/oauth2cli v1.14.0 + github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 github.com/spf13/cobra v1.7.0 + github.com/spf13/viper v1.12.0 + golang.org/x/oauth2 v0.4.0 + golang.org/x/sync v0.1.0 k8s.io/cli-runtime v0.27.1 + k8s.io/klog/v2 v2.90.1 ) require ( @@ -49,6 +56,7 @@ require ( github.com/fzipp/gocyclo v0.6.0 // indirect github.com/go-critic/go-critic v0.7.0 // indirect github.com/go-errors/errors v1.4.2 // indirect + github.com/go-jose/go-jose/v3 v3.0.0 // indirect github.com/go-logr/logr v1.2.3 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.1 // indirect @@ -100,6 +108,7 @@ require ( github.com/hyperledger/fabric-protos-go v0.3.0 // indirect github.com/imdario/mergo v0.3.6 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/int128/listener v1.1.0 // indirect github.com/jgautheron/goconst v1.5.1 // indirect github.com/jingyugao/rowserrcheck v1.1.1 // indirect github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af // indirect @@ -175,7 +184,6 @@ require ( github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/spf13/viper v1.12.0 // indirect github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect github.com/stbenjam/no-sprintf-host-port v0.1.1 // indirect github.com/stretchr/objx v0.5.0 // indirect @@ -207,8 +215,6 @@ require ( golang.org/x/exp/typeparams v0.0.0-20230224173230-c95f2b4c22f2 // indirect golang.org/x/mod v0.9.0 // indirect golang.org/x/net v0.8.0 // indirect - golang.org/x/oauth2 v0.4.0 // indirect - golang.org/x/sync v0.1.0 // indirect golang.org/x/sys v0.6.0 // indirect golang.org/x/term v0.6.0 // indirect golang.org/x/text v0.8.0 // indirect @@ -226,7 +232,6 @@ require ( k8s.io/api v0.27.1 // indirect k8s.io/apimachinery v0.27.1 // indirect k8s.io/client-go v0.27.1 // indirect - k8s.io/klog/v2 v2.90.1 // indirect k8s.io/kube-openapi v0.0.0-20230308215209-15aac26d736a // indirect k8s.io/utils v0.0.0-20230209194617-a36077c30491 // indirect mvdan.cc/gofumpt v0.4.0 // indirect diff --git a/go.sum b/go.sum index 97d900e..93e70b4 100644 --- a/go.sum +++ b/go.sum @@ -27,6 +27,7 @@ cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvf cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= @@ -109,6 +110,8 @@ github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnht github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-oidc/v3 v3.5.0 h1:VxKtbccHZxs8juq7RdJntSqtXFtde9YpNpGn0yqgEHw= +github.com/coreos/go-oidc/v3 v3.5.0/go.mod h1:ecXRtV4romGPeO6ieExAsUK9cb/3fp9hXNz1tlv8PIM= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= @@ -156,6 +159,8 @@ github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3Bop github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo= +github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= @@ -352,6 +357,10 @@ github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/int128/listener v1.1.0 h1:2Jb41DWLpkQ3I9bIdBzO8H/tNwMvyl/OBZWtCV5Pjuw= +github.com/int128/listener v1.1.0/go.mod h1:68WkmTN8PQtLzc9DucIaagAKeGVyMnyyKIkW4Xn47UA= +github.com/int128/oauth2cli v1.14.0 h1:r63NoO10ybUXIXUQxih8WOmt5HQpJubdTmhWh22B9VE= +github.com/int128/oauth2cli v1.14.0/go.mod h1:LIoVAzgAsS2tDDBc8yopkcgY5oZR0+MJAeECkCwtxhA= github.com/jgautheron/goconst v1.5.1 h1:HxVbL1MhydKs8R8n/HE5NPvzfaYmQJA3o879lE4+WcM= github.com/jgautheron/goconst v1.5.1/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4= github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs= @@ -491,6 +500,8 @@ github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwb github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -681,6 +692,7 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= @@ -777,6 +789,7 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= @@ -791,6 +804,8 @@ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.3.0/go.mod h1:rQrIauxkUhJ6CuwEXwymO2/eh4xz2ZWF1nBkcxS+tGk= golang.org/x/oauth2 v0.4.0 h1:NF0gk8LVPg1Ml7SSbGyySuoxdsXitj7TvgvuRxIMc/M= golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -855,6 +870,7 @@ golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1074,6 +1090,7 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= diff --git a/pkg/auth/auth.go b/pkg/auth/auth.go new file mode 100644 index 0000000..27b0b69 --- /dev/null +++ b/pkg/auth/auth.go @@ -0,0 +1,226 @@ +/* +Copyright 2023 The Bestchains 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 auth + +import ( + "context" + "crypto/tls" + "fmt" + "net/http" + "time" + + "github.com/bestchains/bc-cli/pkg/common" + gooidc "github.com/coreos/go-oidc/v3/oidc" + "github.com/int128/oauth2cli" + "github.com/pkg/browser" + "golang.org/x/oauth2" + "golang.org/x/sync/errgroup" + "k8s.io/klog/v2" +) + +const ( + localServerSuccessHTML = ` + + + + + 已认证 + + + + +
+

已认证

+

现在您可以关闭该窗口。

+
+ + +` +) + +func Auth(ctx context.Context, config *common.AuthConfig) (authConfig *common.AuthConfig, err error) { + if !config.Enable { + return config, nil + } + enableAuth = true + var client *client + client, err = newClient(ctx, *config) + if err != nil { + return nil, err + } + if config.Expiry != 0 && config.IDToken != "" { + if time.Now().Before(time.Unix(config.Expiry, 0)) { + klog.V(2).Infoln("Parse ID token from config file and try to verify it is valid...") + err = client.verifyIDToken(ctx) + } else { + klog.V(2).Infoln("ID token has expired, try to refresh it...") + err = client.refresh(ctx) + } + if err == nil { + idToken = config.IDToken + return &client.AuthConfig, nil + } + klog.Errorf("failed to verify or refresh ID token: %v", err) + } + return client.newAuthReq(ctx) +} + +func newClient(ctx context.Context, config common.AuthConfig) (*client, error) { + httpClient := &http.Client{ + Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}, // ignore tls verify, because each has its own cert + } + ctx = context.WithValue(ctx, oauth2.HTTPClient, httpClient) + provider, err := gooidc.NewProvider(ctx, config.IssuerURL) + if err != nil { + return nil, fmt.Errorf("oidc discovery error: %w", err) + } + oauth2Config := oauth2.Config{ + ClientID: common.ClientID, + ClientSecret: common.ClientSecret, + Endpoint: oauth2.Endpoint{ + AuthURL: config.IssuerURL + "/auth", + TokenURL: config.IssuerURL + "/token", + }, + Scopes: []string{"openid", "email", "groups", "profile", "offline_access"}, + } + return &client{ + httpClient: httpClient, + oauth2Config: oauth2Config, + provider: provider, + AuthConfig: config, + }, nil +} + +type client struct { + AuthConfig common.AuthConfig + oauth2Config oauth2.Config + provider *gooidc.Provider + httpClient *http.Client +} + +func (c *client) newAuthReq(ctx context.Context) (config *common.AuthConfig, err error) { + ready := make(chan string, 1) + defer close(ready) + cfg := oauth2cli.Config{ + OAuth2Config: c.oauth2Config, + LocalServerReadyChan: ready, + Logf: klog.V(2).Infof, + LocalServerBindAddress: []string{common.LocalBindPort}, + LocalServerSuccessHTML: localServerSuccessHTML, + } + eg, ctx := errgroup.WithContext(ctx) + eg.Go(func() error { + select { + case url := <-ready: + klog.V(2).Infof("Open %s in default browser...", url) + err := browser.OpenURL(url) + if err != nil { + klog.Errorf("could not open the browser: %w", err) + } + return err + case <-ctx.Done(): + return fmt.Errorf("context done while waiting for authorization: %w", ctx.Err()) + } + }) + eg.Go(func() error { + ctx = c.wrapContext(ctx) + token, err := oauth2cli.GetToken(ctx, cfg) + if err != nil { + return fmt.Errorf("could not get a token: %w", err) + } + klog.V(2).Infof("You got a valid token, will expiry in %s", time.Until(token.Expiry)) + c.AuthConfig.RefreshToken = token.RefreshToken + c.AuthConfig.Expiry = token.Expiry.Unix() + return c.verifyToken(ctx, token) + }) + if err := eg.Wait(); err != nil { + klog.Errorf("authorization error: %s", err) + return &c.AuthConfig, err + } + idToken = c.AuthConfig.IDToken + return &c.AuthConfig, nil +} + +func (c *client) verifyToken(ctx context.Context, token *oauth2.Token) error { + idToken, ok := token.Extra("id_token").(string) + if !ok { + return fmt.Errorf("id_token is missing in the token response: %s", token) + } + c.AuthConfig.IDToken = idToken + if err := c.verifyIDToken(ctx); err != nil { + return err + } + return nil +} + +func (c *client) verifyIDToken(ctx context.Context) error { + verifier := c.provider.Verifier(&gooidc.Config{ClientID: common.ClientID}) + _, err := verifier.Verify(ctx, c.AuthConfig.IDToken) + if err != nil { + return fmt.Errorf("could not verify the ID token: %w", err) + } + return nil +} + +func (c *client) wrapContext(ctx context.Context) context.Context { + if c.httpClient != nil { + ctx = context.WithValue(ctx, oauth2.HTTPClient, c.httpClient) + } + return ctx +} + +// refresh sends a refresh token request and returns a token set. +func (c *client) refresh(ctx context.Context) error { + ctx = c.wrapContext(ctx) + currentToken := &oauth2.Token{ + Expiry: time.Now(), + RefreshToken: c.AuthConfig.RefreshToken, + } + source := c.oauth2Config.TokenSource(ctx, currentToken) + token, err := source.Token() + if err != nil { + klog.Errorf("could not refresh the token: %s", err) + return fmt.Errorf("could not refresh the token: %w", err) + } + c.AuthConfig.RefreshToken = token.RefreshToken + c.AuthConfig.Expiry = token.Expiry.Unix() + return c.verifyToken(ctx, token) +} + +var idToken string +var enableAuth bool + +func AddAuthHeader(req *http.Request) { + if enableAuth && idToken != "" { + req.Header.Add("Authorization", "Bearer "+idToken) + } +} diff --git a/pkg/common/config.go b/pkg/common/config.go new file mode 100644 index 0000000..4e58b8c --- /dev/null +++ b/pkg/common/config.go @@ -0,0 +1,65 @@ +/* +Copyright 2023 The Bestchains 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 common + +// note: mapstructure tag is used for viper auto unmashal, +// viper's own restrictions can only use lowercase letters, +// in 2019 the community found this point is not very reasonable, but has not changed +// see https://github.com/spf13/viper/pull/758 + +type Config struct { + Auth AuthConfig `mapstructure:"auth"` + Saas SaasConfig `mapstructure:"saas"` +} + +type AuthConfig struct { + // Enable is the enable flag + Enable bool `mapstructure:"enable"` + // IssuerURL is the URL of the OIDC issuer. + IssuerURL string `mapstructure:"issuerurl"` + // IDToken is the id-token + IDToken string `mapstructure:"idtoken"` + // RefreshToken is the refresh-token + RefreshToken string `mapstructure:"refreshtoken"` + // Expiry is the expiry time of the access token + Expiry int64 `mapstructure:"expiry"` +} + +type SaasConfig struct { + Depository Depository `mapstructure:"depository"` +} + +type Depository struct { + Server string `mapstructure:"server"` +} + +const ( + // LocalBindPort is the local bind port, + // If you want to change it, you have to change the configuration in the oidc-server configmap at the same time. + LocalBindPort = "127.0.0.1:26666" + // ClientID for oidc + // If you want to change it, you have to change the configuration in the oidc-server configmap at the same time. + ClientID = "bc-cli" + // ClientSecret for oidc + // If you want to change it, you have to change the configuration in the oidc-server configmap at the same time. + ClientSecret = "bc-cli-cli" + + // ConfigFilePath is the config file path and file name + ConfigFilePath = "$HOME/.bestchains/config" + // ConfigFileType is the config file type + ConfigFileType = "yaml" +) diff --git a/pkg/depository/get.go b/pkg/depository/get.go index 9072d70..00110e1 100644 --- a/pkg/depository/get.go +++ b/pkg/depository/get.go @@ -26,6 +26,7 @@ import ( "github.com/bestchains/bc-cli/pkg/printer" uhttp "github.com/bestchains/bc-cli/pkg/utils/http" "github.com/spf13/cobra" + "github.com/spf13/viper" ) func ConstructQuery(cmd *cobra.Command) string { @@ -61,8 +62,7 @@ func NewGetDepositoryCmd(option common.Options) *cobra.Command { Use: "depository [KID]", Short: "Get one or more depositories", RunE: func(cmd *cobra.Command, args []string) error { - // FIXME: the host should be read from the configuration file. - host, _ := cmd.Flags().GetString("host") + host := viper.GetString("saas.depository.server") if host == "" { return fmt.Errorf("no host provided") } @@ -118,6 +118,7 @@ func NewGetDepositoryCmd(option common.Options) *cobra.Command { cmd.Flags().StringP("name", "n", "", "search depository by name") cmd.Flags().StringP("contentName", "c", "", "search depository by content name") cmd.Flags().StringP("host", "", "", "bc-saas server") + _ = viper.BindPFlag("saas.depository.server", cmd.Flags().Lookup("host")) return cmd } diff --git a/pkg/utils/http/get.go b/pkg/utils/http/get.go index 3819c78..6cea077 100644 --- a/pkg/utils/http/get.go +++ b/pkg/utils/http/get.go @@ -22,6 +22,8 @@ import ( "fmt" "io" "net/http" + + "github.com/bestchains/bc-cli/pkg/auth" ) func Do(_url, method string, headers map[string]string, body []byte) ([]byte, error) { @@ -32,6 +34,7 @@ func Do(_url, method string, headers map[string]string, body []byte) ([]byte, er for k, v := range headers { req.Header.Set(k, v) } + auth.AddAuthHeader(req) c := &http.Client{ Transport: &http.Transport{