diff --git a/.github/workflows/go_test.yml b/.github/workflows/go_test.yml index 50c4821b..8bfefef3 100644 --- a/.github/workflows/go_test.yml +++ b/.github/workflows/go_test.yml @@ -4,21 +4,21 @@ jobs: test: strategy: matrix: - go-version: [1.23.x,1.24.x] + go-version: [1.24.x, 1.25.x] os: [ubuntu-latest] runs-on: ${{ matrix.os }} steps: - - name: Install Go - uses: actions/setup-go@v5 - with: - go-version: ${{ matrix.go-version }} - - name: Checkout code - uses: actions/checkout@v4 - - name: Test - run: | - cd auth_server - go test ./... - - name: Build - run: | - cd auth_server - make + - name: Install Go + uses: actions/setup-go@v5 + with: + go-version: ${{ matrix.go-version }} + - name: Checkout code + uses: actions/checkout@v4 + - name: Test + run: | + cd auth_server + go test ./... + - name: Build + run: | + cd auth_server + make diff --git a/auth_server/authn/ldap_auth.go b/auth_server/authn/ldap_auth.go index cc837cd9..f7f85777 100644 --- a/auth_server/authn/ldap_auth.go +++ b/auth_server/authn/ldap_auth.go @@ -61,7 +61,7 @@ func NewLDAPAuth(c *LDAPAuthConfig) (*LDAPAuth, error) { }, nil } -//How to authenticate user, please refer to https://github.com/go-ldap/ldap/blob/master/example_test.go#L166 +// How to authenticate user, please refer to https://github.com/go-ldap/ldap/blob/master/example_test.go#L166 func (la *LDAPAuth) Authenticate(account string, password api.PasswordString) (bool, api.Labels, error) { if account == "" || password == "" { return false, nil, api.NoMatch @@ -160,10 +160,10 @@ func (la *LDAPAuth) bindInitialAsUser(l *ldap.Conn, account string, password api return nil } -//To prevent LDAP injection, some characters must be escaped for searching -//e.g. char '\' will be replaced by hex '\5c' -//Filter meta chars are choosen based on filter complier code -//https://github.com/go-ldap/ldap/blob/master/filter.go#L159 +// To prevent LDAP injection, some characters must be escaped for searching +// e.g. char '\' will be replaced by hex '\5c' +// Filter meta chars are choosen based on filter complier code +// https://github.com/go-ldap/ldap/blob/master/filter.go#L159 func (la *LDAPAuth) escapeAccountInput(account string) string { r := strings.NewReplacer( `\`, `\5c`, @@ -229,8 +229,8 @@ func (la *LDAPAuth) getFilter(account string) string { return filter } -//ldap search and return required attributes' value from searched entries -//default return entry's DN value if you leave attrs array empty +// ldap search and return required attributes' value from searched entries +// default return entry's DN value if you leave attrs array empty func (la *LDAPAuth) ldapSearch(l *ldap.Conn, baseDN *string, filter *string, attrs *[]string) (string, map[string][]string, error) { if l == nil { return "", nil, fmt.Errorf("No ldap connection!") diff --git a/auth_server/authn/mongo_auth.go b/auth_server/authn/mongo_auth.go index db546be4..3291ac97 100644 --- a/auth_server/authn/mongo_auth.go +++ b/auth_server/authn/mongo_auth.go @@ -102,8 +102,7 @@ func (mauth *MongoAuth) authenticate(account string, password api.PasswordString var dbUserRecord authUserEntry collection := mauth.session.Database(mauth.config.MongoConfig.DialInfo.Database).Collection(mauth.config.Collection) - - filter := bson.D{{"username", account}} + filter := bson.D{{"username", account}} err := collection.FindOne(context.TODO(), filter).Decode(&dbUserRecord) // If we connect and get no results we return a NoMatch so auth can fall-through diff --git a/auth_server/authn/rpc_auth.go b/auth_server/authn/rpc_auth.go new file mode 100644 index 00000000..ccf3460d --- /dev/null +++ b/auth_server/authn/rpc_auth.go @@ -0,0 +1,115 @@ +/* + Copyright 2019 Cesanta Software Ltd. + + 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 authn + +import ( + "fmt" + "os/exec" + + "github.com/cesanta/glog" + rpc "github.com/hashicorp/go-plugin" + + "github.com/cesanta/docker_auth/auth_server/api" + shared "github.com/cesanta/docker_auth/auth_server/plugin" + plugin "github.com/cesanta/docker_auth/auth_server/plugin/authn" +) + +type RPCAuthnConfig struct { + Command string `yaml:"command"` + Args []string `yaml:"args"` +} + +func (c *RPCAuthnConfig) Validate() error { + if c.Command == "" { + return fmt.Errorf("command is not set") + } + + if _, err := exec.LookPath(c.Command); err != nil { + return fmt.Errorf("no such command: %s: %w", c.Command, err) + } + + return nil +} + +type RPCAuthn struct { + client *rpc.Client + impl plugin.Authenticator +} + +func (c *RPCAuthn) Authenticate(username string, password api.PasswordString) (bool, api.Labels, error) { + req := &plugin.AuthenticateRequest{ + Username: username, + Password: string(password), + } + resp, err := c.impl.Authenticate(req) + switch { + case err == nil: + return true, api.Labels(resp), nil + case shared.IsError(err, shared.ErrUnauthorized): + return false, nil, nil + case shared.IsError(err, shared.ErrUnacceptable): + return false, nil, api.NoMatch + default: + return false, nil, err + } +} + +func (c *RPCAuthn) Stop() { + if c.client != nil { + c.client.Kill() + } +} + +func (c *RPCAuthn) Name() string { + return "rpc" +} + +func NewRPCAuthn(cfg *RPCAuthnConfig) (*RPCAuthn, error) { + glog.Infof("RPC authenticator: %s", cfg) + + conn := &rpc.ClientConfig{ + HandshakeConfig: plugin.Handshake, + Plugins: plugin.PluginMap, + Cmd: exec.Command(cfg.Command, cfg.Args...), + } + client := rpc.NewClient(conn) + + rpcClient, err := client.Client() + if err != nil { + client.Kill() + return nil, err + } + + raw, err := rpcClient.Dispense(plugin.PluginNetRPC) + if err != nil { + client.Kill() + return nil, err + } + + impl, ok := raw.(plugin.Authenticator) + if !ok { + client.Kill() + return nil, fmt.Errorf("no authenticator plugin provided: %T", impl) + } + + result := &RPCAuthn{ + client: client, + impl: impl, + } + + return result, nil +} diff --git a/auth_server/authn/tokendb_gcs.go b/auth_server/authn/tokendb_gcs.go index 53a0d278..bc2ccc91 100644 --- a/auth_server/authn/tokendb_gcs.go +++ b/auth_server/authn/tokendb_gcs.go @@ -51,8 +51,8 @@ func NewGCSTokenDB(options *GCSStoreConfig) (TokenDB, error) { } type gcsTokenDB struct { - gcs *storage.Client - bucket string + gcs *storage.Client + bucket string tokenHashCost int } diff --git a/auth_server/authn/tokendb_level.go b/auth_server/authn/tokendb_level.go index 66d43444..f13c71a2 100644 --- a/auth_server/authn/tokendb_level.go +++ b/auth_server/authn/tokendb_level.go @@ -37,8 +37,8 @@ const ( var ExpiredToken = errors.New("expired token") type LevelDBStoreConfig struct { - Path string `yaml:"path,omitempty"` - TokenHashCost int `yaml:"token_hash_cost,omitempty"` + Path string `yaml:"path,omitempty"` + TokenHashCost int `yaml:"token_hash_cost,omitempty"` } // TokenDB stores tokens using LevelDB diff --git a/auth_server/authn/tokendb_redis.go b/auth_server/authn/tokendb_redis.go index 39a4f10a..5288f635 100644 --- a/auth_server/authn/tokendb_redis.go +++ b/auth_server/authn/tokendb_redis.go @@ -42,7 +42,6 @@ type RedisClient interface { } // NewRedisTokenDB returns a new TokenDB structure which uses Redis as the storage backend. -// func NewRedisTokenDB(options *RedisStoreConfig) (TokenDB, error) { var client RedisClient if options.ClusterOptions != nil { @@ -58,11 +57,11 @@ func NewRedisTokenDB(options *RedisStoreConfig) (TokenDB, error) { tokenHashCost = bcrypt.DefaultCost } - return &redisTokenDB{client,tokenHashCost}, nil + return &redisTokenDB{client, tokenHashCost}, nil } type redisTokenDB struct { - client RedisClient + client RedisClient tokenHashCost int } diff --git a/auth_server/authn/xorm_sqlite_authn.go b/auth_server/authn/xorm_sqlite_authn.go index f1a39ccc..e0cd4687 100644 --- a/auth_server/authn/xorm_sqlite_authn.go +++ b/auth_server/authn/xorm_sqlite_authn.go @@ -1,4 +1,5 @@ -//+build sqlite +//go:build sqlite +// +build sqlite /* Copyright 2020 Cesanta Software Ltd. diff --git a/auth_server/authz/acl_xorm_sqlite.go b/auth_server/authz/acl_xorm_sqlite.go index cdf5b81d..a9ce34f8 100644 --- a/auth_server/authz/acl_xorm_sqlite.go +++ b/auth_server/authz/acl_xorm_sqlite.go @@ -1,4 +1,5 @@ -//+build sqlite +//go:build sqlite +// +build sqlite /* Copyright 2020 Cesanta Software Ltd. diff --git a/auth_server/authz/rpc_auth.go b/auth_server/authz/rpc_auth.go new file mode 100644 index 00000000..305bf3a0 --- /dev/null +++ b/auth_server/authz/rpc_auth.go @@ -0,0 +1,120 @@ +/* + Copyright 2019 Cesanta Software Ltd. + + 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 authz + +import ( + "fmt" + "os/exec" + + "github.com/cesanta/glog" + rpc "github.com/hashicorp/go-plugin" + + "github.com/cesanta/docker_auth/auth_server/api" + shared "github.com/cesanta/docker_auth/auth_server/plugin" + plugin "github.com/cesanta/docker_auth/auth_server/plugin/authz" +) + +type RPCAuthzConfig struct { + Command string `yaml:"command"` + Args []string `yaml:"args"` +} + +func (c *RPCAuthzConfig) Validate() error { + if c.Command == "" { + return fmt.Errorf("command is not set") + } + + if _, err := exec.LookPath(c.Command); err != nil { + return fmt.Errorf("no such command: %s: %w", c.Command, err) + } + + return nil +} + +type RPCAuthz struct { + client *rpc.Client + impl plugin.Authorizer +} + +func (c *RPCAuthz) Authorize(ai *api.AuthRequestInfo) ([]string, error) { + req := &plugin.AuthorizeRequest{ + Account: ai.Account, + Type: ai.Type, + Name: ai.Name, + Service: ai.Service, + IP: ai.IP, + Actions: ai.Actions, + Labels: ai.Labels, + } + resp, err := c.impl.Authorize(req) + switch { + case err == nil: + return resp, nil + case shared.IsError(err, shared.ErrForbidden): + return []string{}, nil + case shared.IsError(err, shared.ErrUnacceptable): + return nil, api.NoMatch + default: + return nil, err + } +} + +func (c *RPCAuthz) Stop() { + if c.client != nil { + c.client.Kill() + } +} + +func (c *RPCAuthz) Name() string { + return "rpc" +} + +func NewRPCAuthz(cfg *RPCAuthzConfig) (*RPCAuthz, error) { + glog.Infof("RPC authorizer: %s", cfg) + + conn := &rpc.ClientConfig{ + HandshakeConfig: plugin.Handshake, + Plugins: plugin.PluginMap, + Cmd: exec.Command(cfg.Command, cfg.Args...), + } + client := rpc.NewClient(conn) + + rpcClient, err := client.Client() + if err != nil { + client.Kill() + return nil, err + } + + raw, err := rpcClient.Dispense(plugin.PluginNetRPC) + if err != nil { + client.Kill() + return nil, err + } + + impl, ok := raw.(plugin.Authorizer) + if !ok { + client.Kill() + return nil, fmt.Errorf("no authorizer plugin provided: %T", impl) + } + + result := &RPCAuthz{ + client: client, + impl: impl, + } + + return result, nil +} diff --git a/auth_server/gen_version.go b/auth_server/gen_version.go index 65c86bda..04d7a998 100644 --- a/auth_server/gen_version.go +++ b/auth_server/gen_version.go @@ -1,4 +1,5 @@ -//+build ignore +//go:build ignore +// +build ignore /* Copyright 2021 Cesanta Software Ltd. diff --git a/auth_server/go.mod b/auth_server/go.mod index 2a245dc6..c0725c39 100644 --- a/auth_server/go.mod +++ b/auth_server/go.mod @@ -1,9 +1,11 @@ module github.com/cesanta/docker_auth/auth_server -go 1.23.0 +go 1.24 + +replace github.com/cesanta/docker_auth/auth_server/plugin => ./plugin require ( - cloud.google.com/go/storage v1.29.0 + cloud.google.com/go/storage v1.30.1 github.com/casbin/casbin/v2 v2.55.1 github.com/cesanta/glog v0.0.0-20150527111657-22eb27a0ae19 github.com/coreos/go-oidc/v3 v3.9.0 @@ -20,8 +22,8 @@ require ( go.mongodb.org/mongo-driver v1.10.2 golang.org/x/crypto v0.36.0 golang.org/x/net v0.38.0 - golang.org/x/oauth2 v0.13.0 - google.golang.org/api v0.126.0 + golang.org/x/oauth2 v0.14.0 + google.golang.org/api v0.149.0 gopkg.in/fsnotify.v1 v1.4.7 gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 gopkg.in/yaml.v2 v2.4.0 @@ -29,11 +31,13 @@ require ( ) require ( - cloud.google.com/go v0.110.2 // indirect - cloud.google.com/go/compute v1.20.1 // indirect + cloud.google.com/go v0.110.10 // indirect + cloud.google.com/go/compute v1.23.3 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect - cloud.google.com/go/iam v0.13.0 // indirect + cloud.google.com/go/iam v1.1.5 // indirect github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible // indirect + github.com/cesanta/docker_auth/auth_server/plugin v0.0.0-00010101000000-000000000000 // indirect + github.com/fatih/color v1.13.0 // indirect github.com/go-jose/go-jose/v3 v3.0.4 // indirect github.com/goccy/go-json v0.9.11 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect @@ -41,17 +45,23 @@ require ( github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/go-cmp v0.6.0 // indirect - github.com/google/s2a-go v0.1.4 // indirect - github.com/google/uuid v1.3.0 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect - github.com/googleapis/gax-go/v2 v2.11.0 // indirect + github.com/google/s2a-go v0.1.7 // indirect + github.com/google/uuid v1.4.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect + github.com/googleapis/gax-go/v2 v2.12.0 // indirect github.com/gorilla/mux v1.8.0 // indirect + github.com/hashicorp/go-hclog v1.6.3 // indirect + github.com/hashicorp/go-plugin v1.7.0 // indirect + github.com/hashicorp/yamux v0.1.2 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.15.11 // indirect github.com/kr/pretty v0.3.0 // indirect + github.com/mattn/go-colorable v0.1.12 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/montanaflynn/stats v0.6.6 // indirect + github.com/oklog/run v1.1.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/sirupsen/logrus v1.9.0 // indirect @@ -65,11 +75,11 @@ require ( golang.org/x/text v0.23.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect - google.golang.org/grpc v1.56.3 // indirect - google.golang.org/protobuf v1.33.0 // indirect + google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 // indirect + google.golang.org/grpc v1.61.0 // indirect + google.golang.org/protobuf v1.36.6 // indirect gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect lukechampine.com/uint128 v1.2.0 // indirect diff --git a/auth_server/go.sum b/auth_server/go.sum index e956374b..2c62095e 100644 --- a/auth_server/go.sum +++ b/auth_server/go.sum @@ -2,14 +2,22 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.110.2 h1:sdFPBr6xG9/wkBbfhmUz/JmZC7X6LavQgcrVINrKiVA= cloud.google.com/go v0.110.2/go.mod h1:k04UEeEtb6ZBRTv3dZz4CeJC3jKGxyhl0sAiVVquxiw= +cloud.google.com/go v0.110.10 h1:LXy9GEO+timppncPIAZoOj3l58LIU9k+kn48AN7IO3Y= +cloud.google.com/go v0.110.10/go.mod h1:v1OoFqYxiBkUrruItNM3eT4lLByNjxmJSV/xDKJNnic= cloud.google.com/go/compute v1.20.1 h1:6aKEtlUiwEpJzM001l0yFkpXmUVXaN8W+fbkb2AZNbg= cloud.google.com/go/compute v1.20.1/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= +cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= +cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/iam v0.13.0 h1:+CmB+K0J/33d0zSQ9SlFWUeCCEn5XJA0ZMZ3pHE9u8k= cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0= +cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI= +cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8= cloud.google.com/go/storage v1.29.0 h1:6weCgzRvMg7lzuUurI4697AqIRPU1SvzHhynwpW31jI= cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4= +cloud.google.com/go/storage v1.30.1 h1:uOdMxAs8HExqBlnLtnQyP0YkvbiDpdGShGKtx6U/oNM= +cloud.google.com/go/storage v1.30.1/go.mod h1:NfxhC0UJE1aXSx7CIIbCf7y9HKT7BiccwkR7+P7gN8E= gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s= gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU= gitee.com/travelliu/dm v1.8.11192/go.mod h1:DHTzyhCrM843x9VdKVbZ+GKXGRbKM2sJ4LxihRxShkE= @@ -95,6 +103,8 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= @@ -175,14 +185,22 @@ github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc= github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= +github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k= github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= +github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= github.com/googleapis/gax-go/v2 v2.11.0 h1:9V9PWXEsWnPpQhu/PeQIkS4eGzMlTLGgt80cUUI8Ki4= github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI= +github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= +github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= @@ -198,9 +216,13 @@ github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoP github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-plugin v1.7.0 h1:YghfQH/0QmPNc/AZMTFE3ac8fipZyZECHdDPshfk+mA= +github.com/hashicorp/go-plugin v1.7.0/go.mod h1:BExt6KEaIYx804z8k4gRzRLEvxKVb+kn0NMcihqOqb8= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= @@ -214,6 +236,8 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8= +github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= @@ -308,6 +332,9 @@ github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaO github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= @@ -315,8 +342,11 @@ github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= @@ -350,6 +380,8 @@ github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxzi github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= +github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= @@ -442,6 +474,7 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= @@ -556,6 +589,8 @@ golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY= golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= +golang.org/x/oauth2 v0.14.0 h1:P0Vrf/2538nmC0H+pEQ3MNFRRnVR7RlqyVw+bvm26z0= +golang.org/x/oauth2 v0.14.0/go.mod h1:lAtNWgaWfL4cm7j2OV8TxGi9Qb7ECORx8DktCY74OwM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -597,8 +632,11 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/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-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210902050250-f475640dd07b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -662,6 +700,8 @@ golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNq google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.126.0 h1:q4GJq+cAdMAC7XP7njvQ4tvohGLiSlytuL4BQxbIZ+o= google.golang.org/api v0.126.0/go.mod h1:mBwVAtz+87bEN6CbA1GtZPDOqY2R5ONPqJeIlvyo4Aw= +google.golang.org/api v0.149.0 h1:b2CqT6kG+zqJIVKRQ3ELJVLN1PwHZ6DJ3dW8yl82rgY= +google.golang.org/api v0.149.0/go.mod h1:Mwn1B7JTXrzXtnvmzQE2BD6bYZQ8DShKZDZbeN9I7qI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -676,10 +716,16 @@ google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc h1:8DyZCyvI8mE1IdLy/60bS+52xfymkE72wv1asokgtao= google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:xZnkP7mREFX5MORlOPEzLMr+90PPZQ2QWzrVTWfAq64= +google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ= +google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy1+IPwWHZUzoD0IccYZIrXILAQpc+Qy9CMhY= google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc h1:kVKPf/IiYSBWEWtkIn6wZXwWGCnLKcC8oWfZvXjsGnM= google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= +google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 h1:JpwMPBpFN3uKhdaekDpiNlImDdkUAyiJ6ez/uxGaUSo= +google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4= google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc h1:XSJ8Vk1SWuNr8S18z1NZSziL0CPIXLCCMDOEFtHBOFc= google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 h1:Jyp0Hsi0bmHXG6k9eATXoYtjd6e2UzZ1SCn/wIupY14= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:oQ5rr10WTTMvP4A36n8JpR1OrO1BEiV4f78CneXZxkA= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= @@ -697,6 +743,8 @@ google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc= google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= +google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0= +google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -710,6 +758,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d h1:TxyelI5cVkbREznMhfzycHdkp5cLA7DpE+GKjSslYhM= gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= diff --git a/auth_server/plugin/authn/contract.go b/auth_server/plugin/authn/contract.go new file mode 100644 index 00000000..d315ff62 --- /dev/null +++ b/auth_server/plugin/authn/contract.go @@ -0,0 +1,35 @@ +/* + Copyright 2019 Cesanta Software Ltd. + + 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 authn + +// AuthenticateResponse contains information associated with +// the authenticated principal. +type AuthenticateResponse map[string][]string + +// AuthenticateRequest represents the input query for authentication requests. +type AuthenticateRequest struct { + // Username is the authentication principal + Username string + // Password is the authentication secret + Password string +} + +// Authenticator is the contract plugin implementations must fulfill +// in order to be used for authentication purposes. +type Authenticator interface { + Authenticate(*AuthenticateRequest) (AuthenticateResponse, error) +} diff --git a/auth_server/plugin/authn/doc.go b/auth_server/plugin/authn/doc.go new file mode 100644 index 00000000..d8d1d4bf --- /dev/null +++ b/auth_server/plugin/authn/doc.go @@ -0,0 +1,18 @@ +/* + Copyright 2019 Cesanta Software Ltd. + + 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. +*/ + +// Shared interfaces and constants for authentication requests +package authn diff --git a/auth_server/plugin/authn/handshake.go b/auth_server/plugin/authn/handshake.go new file mode 100644 index 00000000..4bd5c467 --- /dev/null +++ b/auth_server/plugin/authn/handshake.go @@ -0,0 +1,38 @@ +/* + Copyright 2019 Cesanta Software Ltd. + + 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 authn + +import ( + plugin "github.com/hashicorp/go-plugin" +) + +const ( + // PluginNetRPC is the plugin identifier for the net/rpc implementation + PluginNetRPC = "authenticator" +) + +// PluginMap is the map of plugins we can dispense. +var PluginMap = map[string]plugin.Plugin{ + PluginNetRPC: &RPCPlugin{}, +} + +// Handshake is the plugin contract between the host and its plugins. +var Handshake = plugin.HandshakeConfig{ + ProtocolVersion: 1, + MagicCookieKey: "AUTHN_PLUGIN", + MagicCookieValue: "docker_auth", +} diff --git a/auth_server/plugin/authn/rpc.go b/auth_server/plugin/authn/rpc.go new file mode 100644 index 00000000..6d814b2d --- /dev/null +++ b/auth_server/plugin/authn/rpc.go @@ -0,0 +1,86 @@ +/* + Copyright 2019 Cesanta Software Ltd. + + 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 authn + +import ( + "net/rpc" + + plugin "github.com/hashicorp/go-plugin" +) + +// RPCPlugin implements [plugin.Plugin] +// using net/rpc as transport implementation. +type RPCPlugin struct { + impl Authenticator +} + +func NewRPCPlugin(a Authenticator) *RPCPlugin { + result := &RPCPlugin{ + impl: a, + } + + return result +} + +func (p *RPCPlugin) Server(_ *plugin.MuxBroker) (interface{}, error) { + return NewRPCServer(p.impl), nil +} + +func (*RPCPlugin) Client(_ *plugin.MuxBroker, c *rpc.Client) (interface{}, error) { + return NewRPCClient(c), nil +} + +// RPCClient implements [Authenticator] using an [rpc.Client] +// for communication. +type RPCClient struct { + client *rpc.Client +} + +func NewRPCClient(c *rpc.Client) *RPCClient { + result := &RPCClient{ + client: c, + } + + return result +} + +func (c *RPCClient) Authenticate(req *AuthenticateRequest) (resp AuthenticateResponse, err error) { + err = c.client.Call("Plugin.Authenticate", req, &resp) + + return +} + +// RPCServer is the server side of the communication with RPCClient, conforming to +// the requirements of net/rpc +type RPCServer struct { + impl Authenticator +} + +func NewRPCServer(a Authenticator) *RPCServer { + result := &RPCServer{ + impl: a, + } + + return result +} + +func (s *RPCServer) Authenticate(req *AuthenticateRequest, resp *AuthenticateResponse) error { + v, err := s.impl.Authenticate(req) + *resp = v + + return err +} diff --git a/auth_server/plugin/authz/contract.go b/auth_server/plugin/authz/contract.go new file mode 100644 index 00000000..150f9ce7 --- /dev/null +++ b/auth_server/plugin/authz/contract.go @@ -0,0 +1,42 @@ +/* + Copyright 2019 Cesanta Software Ltd. + + 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 authz + +import ( + "net" +) + +// AuthorizeResponse contains information associated with +// the authorized principal. +type AuthorizeResponse []string + +// AuthorizeRequest represents the input query for authorization requests. +type AuthorizeRequest struct { + Account string + Type string + Name string + Service string + IP net.IP + Actions []string + Labels map[string][]string +} + +// Authorizer is the contract plugin implementations must fulfill +// in order to be used for authorization purposes. +type Authorizer interface { + Authorize(*AuthorizeRequest) (AuthorizeResponse, error) +} diff --git a/auth_server/plugin/authz/doc.go b/auth_server/plugin/authz/doc.go new file mode 100644 index 00000000..a6d29588 --- /dev/null +++ b/auth_server/plugin/authz/doc.go @@ -0,0 +1,18 @@ +/* + Copyright 2019 Cesanta Software Ltd. + + 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. +*/ + +// Shared interfaces and constants for authorization requests +package authz diff --git a/auth_server/plugin/authz/handshake.go b/auth_server/plugin/authz/handshake.go new file mode 100644 index 00000000..da5f4b7d --- /dev/null +++ b/auth_server/plugin/authz/handshake.go @@ -0,0 +1,38 @@ +/* + Copyright 2019 Cesanta Software Ltd. + + 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 authz + +import ( + plugin "github.com/hashicorp/go-plugin" +) + +const ( + // PluginNetRPC is the plugin identifier for the net/rpc implementation + PluginNetRPC = "authorizer" +) + +// PluginMap is the map of plugins we can dispense. +var PluginMap = map[string]plugin.Plugin{ + PluginNetRPC: &RPCPlugin{}, +} + +// Handshake is the plugin contract between the host and its plugins. +var Handshake = plugin.HandshakeConfig{ + ProtocolVersion: 1, + MagicCookieKey: "AUTHZ_PLUGIN", + MagicCookieValue: "docker_auth", +} diff --git a/auth_server/plugin/authz/rpc.go b/auth_server/plugin/authz/rpc.go new file mode 100644 index 00000000..6e47a579 --- /dev/null +++ b/auth_server/plugin/authz/rpc.go @@ -0,0 +1,86 @@ +/* + Copyright 2019 Cesanta Software Ltd. + + 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 authz + +import ( + "net/rpc" + + plugin "github.com/hashicorp/go-plugin" +) + +// RPCPlugin implements [plugin.Plugin] +// using net/rpc as transport implementation. +type RPCPlugin struct { + impl Authorizer +} + +func NewRPCPlugin(a Authorizer) *RPCPlugin { + result := &RPCPlugin{ + impl: a, + } + + return result +} + +func (p *RPCPlugin) Server(_ *plugin.MuxBroker) (interface{}, error) { + return NewRPCServer(p.impl), nil +} + +func (*RPCPlugin) Client(_ *plugin.MuxBroker, c *rpc.Client) (interface{}, error) { + return NewRPCClient(c), nil +} + +// RPCClient implements [Authorizer] using an [rpc.Client] +// for communication. +type RPCClient struct { + client *rpc.Client +} + +func NewRPCClient(c *rpc.Client) *RPCClient { + result := &RPCClient{ + client: c, + } + + return result +} + +func (c *RPCClient) Authorize(req *AuthorizeRequest) (resp AuthorizeResponse, err error) { + err = c.client.Call("Plugin.Authorize", req, &resp) + + return +} + +// RPCServer is the server side of the communication with RPCClient, conforming to +// the requirements of net/rpc +type RPCServer struct { + impl Authorizer +} + +func NewRPCServer(a Authorizer) *RPCServer { + result := &RPCServer{ + impl: a, + } + + return result +} + +func (s *RPCServer) Authorize(req *AuthorizeRequest, resp *AuthorizeResponse) error { + v, err := s.impl.Authorize(req) + *resp = v + + return err +} diff --git a/auth_server/plugin/doc.go b/auth_server/plugin/doc.go new file mode 100644 index 00000000..066c5ce2 --- /dev/null +++ b/auth_server/plugin/doc.go @@ -0,0 +1,19 @@ +/* + Copyright 2019 Cesanta Software Ltd. + + 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. +*/ + +// Shared constants, interfaces and other parts for implementing +// authentication/authorization plugins via RPC +package plugin diff --git a/auth_server/plugin/errors.go b/auth_server/plugin/errors.go new file mode 100644 index 00000000..174569a0 --- /dev/null +++ b/auth_server/plugin/errors.go @@ -0,0 +1,27 @@ +package plugin + +import ( + "errors" +) + +var ( + ErrForbidden = errors.New("authorization has been refused") + ErrUnauthorized = errors.New("authentication has been refused") + ErrUnacceptable = errors.New("plugin is not applicable to the provided input") +) + +// IsError is a helper method to compare errors that have +// been serialized and transmitted via RPC. the recommended +// way to compare errors using [errors#Is] does not work as +// the deserialized error and the comparison input are two +// distinct error instances (presumably created via [errors.New]) +// that happen to have the same error message but have different +// pointer addresses. IsError compares the message of the +// provided errors without unwrapping either of them. +func IsError(err, target error) bool { + if err == nil || target == nil { + return err == target + } + + return err.Error() == target.Error() +} diff --git a/auth_server/plugin/go.mod b/auth_server/plugin/go.mod new file mode 100644 index 00000000..a1bfa4bc --- /dev/null +++ b/auth_server/plugin/go.mod @@ -0,0 +1,3 @@ +module github.com/cesanta/docker_auth/auth_server/plugin + +go 1.23.0 diff --git a/auth_server/server/config.go b/auth_server/server/config.go index 13c610b7..de524e0b 100644 --- a/auth_server/server/config.go +++ b/auth_server/server/config.go @@ -51,11 +51,13 @@ type Config struct { MongoAuth *authn.MongoAuthConfig `yaml:"mongo_auth,omitempty"` XormAuthn *authn.XormAuthnConfig `yaml:"xorm_auth,omitempty"` ExtAuth *authn.ExtAuthConfig `yaml:"ext_auth,omitempty"` + RPCAuthn *authn.RPCAuthnConfig `yaml:"rpc_authn,omitempty"` PluginAuthn *authn.PluginAuthnConfig `yaml:"plugin_authn,omitempty"` ACL authz.ACL `yaml:"acl,omitempty"` ACLMongo *authz.ACLMongoConfig `yaml:"acl_mongo,omitempty"` ACLXorm *authz.XormAuthzConfig `yaml:"acl_xorm,omitempty"` ExtAuthz *authz.ExtAuthzConfig `yaml:"ext_authz,omitempty"` + RPCAuthz *authz.RPCAuthzConfig `yaml:"rpc_authz,omitempty"` PluginAuthz *authz.PluginAuthzConfig `yaml:"plugin_authz,omitempty"` CasbinAuthz *authz.CasbinAuthzConfig `yaml:"casbin_authz,omitempty"` } @@ -180,7 +182,7 @@ func validate(c *Config) error { if c.Token.Expiration <= 0 { return fmt.Errorf("expiration must be positive, got %d", c.Token.Expiration) } - if c.Users == nil && c.ExtAuth == nil && c.GoogleAuth == nil && c.GitHubAuth == nil && c.GitlabAuth == nil && c.OIDCAuth == nil && c.LDAPAuth == nil && c.MongoAuth == nil && c.XormAuthn == nil && c.PluginAuthn == nil { + if c.Users == nil && c.ExtAuth == nil && c.RPCAuthn == nil && c.GoogleAuth == nil && c.GitHubAuth == nil && c.GitlabAuth == nil && c.OIDCAuth == nil && c.LDAPAuth == nil && c.MongoAuth == nil && c.XormAuthn == nil && c.PluginAuthn == nil { return errors.New("no auth methods are configured, this is probably a mistake. Use an empty user map if you really want to deny everyone") } if c.MongoAuth != nil { @@ -308,7 +310,12 @@ func validate(c *Config) error { return fmt.Errorf("bad ext_auth config: %s", err) } } - if c.ACL == nil && c.ACLXorm == nil && c.ACLMongo == nil && c.ExtAuthz == nil && c.PluginAuthz == nil { + if c.RPCAuthn != nil { + if err := c.RPCAuthn.Validate(); err != nil { + return fmt.Errorf("bad rpc_authn config: %s", err) + } + } + if c.ACL == nil && c.ACLXorm == nil && c.ACLMongo == nil && c.ExtAuthz == nil && c.RPCAuthz == nil && c.PluginAuthz == nil { return errors.New("ACL is empty, this is probably a mistake. Use an empty list if you really want to deny all actions") } @@ -332,6 +339,11 @@ func validate(c *Config) error { return err } } + if c.RPCAuthz != nil { + if err := c.RPCAuthz.Validate(); err != nil { + return fmt.Errorf("bad rpc_authz config: %w", err) + } + } if c.PluginAuthn != nil { if err := c.PluginAuthn.Validate(); err != nil { return fmt.Errorf("bad plugin_authn config: %s", err) diff --git a/auth_server/server/server.go b/auth_server/server/server.go index ae7abd82..dadccffe 100644 --- a/auth_server/server/server.go +++ b/auth_server/server/server.go @@ -82,12 +82,26 @@ func NewAuthServer(c *Config) (*AuthServer, error) { extAuthorizer := authz.NewExtAuthzAuthorizer(c.ExtAuthz) as.authorizers = append(as.authorizers, extAuthorizer) } + if c.RPCAuthz != nil { + ra, err := authz.NewRPCAuthz(c.RPCAuthz) + if err != nil { + return nil, err + } + as.authorizers = append(as.authorizers, ra) + } if c.Users != nil { as.authenticators = append(as.authenticators, authn.NewStaticUserAuth(c.Users)) } if c.ExtAuth != nil { as.authenticators = append(as.authenticators, authn.NewExtAuth(c.ExtAuth)) } + if c.RPCAuthn != nil { + ra, err := authn.NewRPCAuthn(c.RPCAuthn) + if err != nil { + return nil, err + } + as.authenticators = append(as.authenticators, ra) + } if c.GoogleAuth != nil { ga, err := authn.NewGoogleAuth(c.GoogleAuth) if err != nil { @@ -437,7 +451,7 @@ func (as *AuthServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) { case req.URL.Path == path_prefix+"/auth": as.doAuth(rw, req) case req.URL.Path == path_prefix+"/auth/token": - as.doAuth(rw, req) + as.doAuth(rw, req) case req.URL.Path == path_prefix+"/google_auth" && as.ga != nil: as.ga.DoGoogleAuth(rw, req) case req.URL.Path == path_prefix+"/github_auth" && as.gha != nil: diff --git a/examples/reference.yml b/examples/reference.yml index 31de9ee7..54c13558 100644 --- a/examples/reference.yml +++ b/examples/reference.yml @@ -10,7 +10,7 @@ # autoredirect: false # rootcertbundle: "/path/to/server.pem" -server: # Server settings. +server: # Server settings. # Address to listen on. # Can be HOST:PORT for TCP or file path (e.g. /run/docker_auth.sock) for Unix socket. addr: ":5001" @@ -76,8 +76,8 @@ server: # Server settings. # end of addresses. # real_ip_pos: -2 -token: # Settings for the tokens. - issuer: "Acme auth server" # Must match issuer in the Registry config. +token: # Settings for the tokens. + issuer: "Acme auth server" # Must match issuer in the Registry config. expiration: 900 # Token must be signed by a certificate that registry trusts, i.e. by a certificate to which a trust chain # can be constructed from one of the certificates in registry's auth.token.rootcertbundle. @@ -98,10 +98,10 @@ token: # Settings for the tokens. users: # Password is specified as a BCrypt hash. Use `htpasswd -nB USERNAME` to generate. "admin": - password: "$2y$05$LO.vzwpWC5LZGqThvEfznu8qhb5SGqvBSWY1J3yZ4AxtMRZ3kN5jC" # badmin + password: "$2y$05$LO.vzwpWC5LZGqThvEfznu8qhb5SGqvBSWY1J3yZ4AxtMRZ3kN5jC" # badmin "test": - password: "$2y$05$WuwBasGDAgr.QCbGIjKJaep4dhxeai9gNZdmBnQXqpKly57oNutya" # 123 - "": {} # Allow anonymous (no "docker login") access. + password: "$2y$05$WuwBasGDAgr.QCbGIjKJaep4dhxeai9gNZdmBnQXqpKly57oNutya" # 123 + "": {} # Allow anonymous (no "docker login") access. # Google authentication. # ==! NB: DO NOT ENTER YOUR GOOGLE PASSWORD AT "docker login". IT WILL NOT WORK. @@ -109,7 +109,7 @@ users: # Go to the server's port as HTTPS with your browser and follow the "Login with Google account" link. # Once signed in, you will get a throw-away password which you can use for Docker login. google_auth: - domain: "example.com" # Optional. If set, only logins from this domain are accepted. + domain: "example.com" # Optional. If set, only logins from this domain are accepted. # client_id and client_secret for API access. Required. # Follow instructions here: https://developers.google.com/identity/sign-in/web/devconsole-project # NB: Make sure JavaScript origins are configured correctly, and that third-party @@ -133,7 +133,7 @@ google_auth: # Go to the server's port as HTTPS with your browser and follow the "Login with GitHub account" link. # Once signed in, you will get a throw-away password which you can use for Docker login. github_auth: - organization: "acme" # Optional. If set, only logins from this organization are accepted. + organization: "acme" # Optional. If set, only logins from this organization are accepted. # client_id and client_secret for API access. Required. # You can register a new application here: https://github.com/settings/developers # NB: Make sure JavaScript origins are configured correctly, and that third-party @@ -155,11 +155,11 @@ github_auth: # or Redis, redis_token_db: redis_options: - # with a single instance, - addr: localhost:6379 + # with a single instance, + addr: localhost:6379 redis_cluster_options: - # or in the cluster mode. - addrs: ["localhost:7000"] + # or in the cluster mode. + addrs: ["localhost:7000"] # How long to wait when talking to GitHub servers. Optional. http_timeout: "10s" # How long to wait before revalidating the GitHub token. Optional. @@ -212,7 +212,6 @@ oidc_auth: - openid - email - # Gitlab authentication. # ==! NB: DO NOT ENTER YOUR Gitlab PASSWORD AT "docker login". IT WILL NOT WORK. # Instead, Auth server maintains a database of Gitlab authentication tokens. @@ -327,9 +326,17 @@ xorm_auth: # The "labels" key may contain labels to be passed down to authz, where they can # be used in matching. See ext_auth.sh for an example. ext_auth: - command: "/usr/local/bin/my_auth" # Can be a relative path too; $PATH works. + command: "/usr/local/bin/my_auth" # Can be a relative path too; $PATH works. args: ["--flag", "--more", "--flags"] +# External authentication via RPC calls. Plugins are started alongside the server and +# are queried via RPC calls. Only a single method must be implemented which responds +# with either a map of labels to further process during the authorization phase or an +# error. +rpc_authn: + command: "/usr/local/libexec/docker_auth/my_authn" + args: ["--log.level", "info"] + # User written authentication plugin - call a user written program to authenticate user. # Username of type string and password of authn.PasswordString is passed to the plugin # Expects a boolean value whether the user is authenticate or not, authn.Labels, error @@ -369,58 +376,58 @@ plugin_authn: # * ${name} - the name of the repository (i.e. image), e.g. centos. # * ${labels: