aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Crute <mike@crute.us>2021-11-16 14:46:24 -0800
committerMike Crute <mike@crute.us>2021-11-17 07:56:10 -0800
commitcc58a3da7d647de8520e33dc4356672d2ed1a366 (patch)
tree1b232a0d51446eb6370cfb13932190d31ce053df
parenta42d794a286154a3106551e6e483861af2a9ef16 (diff)
downloadcloud-identity-broker-cc58a3da7d647de8520e33dc4356672d2ed1a366.tar.bz2
cloud-identity-broker-cc58a3da7d647de8520e33dc4356672d2ed1a366.tar.xz
cloud-identity-broker-cc58a3da7d647de8520e33dc4356672d2ed1a366.zip
Import of source code
-rw-r--r--.gitignore3
-rw-r--r--Dockerfile5
-rw-r--r--Makefile40
-rw-r--r--app/config.go70
-rw-r--r--app/controllers/api.go6
-rw-r--r--app/controllers/api_account_list.go109
-rw-r--r--app/controllers/api_console_redirect.go63
-rw-r--r--app/controllers/api_credentials.go76
-rw-r--r--app/controllers/api_region_list.go61
-rw-r--r--app/controllers/aws.go52
-rw-r--r--app/controllers/basic.go17
-rw-r--r--app/middleware/auth.go212
-rw-r--r--app/models/account.go115
-rw-r--r--app/models/session_key.go202
-rw-r--r--app/models/user.go99
-rw-r--r--auth/github/github.go127
-rw-r--r--auth/jwt.go123
-rw-r--r--cloud/aws/aws.go218
-rw-r--r--cloud/aws/error.go11
-rw-r--r--cmd/web/server.go155
-rw-r--r--go.mod86
-rw-r--r--go.sum899
-rw-r--r--main.go45
-rw-r--r--templates/404.tpl10
-rw-r--r--templates/40x.tpl10
-rw-r--r--templates/50x.tpl10
-rw-r--r--templates/footer.tpl0
-rw-r--r--templates/header.tpl0
-rw-r--r--templates/index.tpl204
29 files changed, 3028 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e57e9f3
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
1/cloud-identity-broker
2/ssl
3/docker
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..947d22f
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,5 @@
1FROM alpine:latest
2
3RUN apk add --no-cache ca-certificates
4COPY aws-access-prod /
5CMD ["/aws-access-prod", "--config", "/config.json"]
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..f8faca0
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,40 @@
1IMAGE=docker.crute.me/aws-access
2BINARY=cloud-identity-broker
3
4$(BINARY): main.go $(shell find . -name '*.go')
5 go build -o $@ $<
6
7.PHONY: vet
8vet: main.go
9 go vet $<
10
11.PHONY: docker
12docker:
13 mkdir docker; cp Dockerfile docker; cd docker; \
14 docker pull nginx:mainline-alpine; \
15 docker build --no-cache -t $(IMAGE):stage .
16
17.PHONY: publish
18publish:
19 docker push $(IMAGE):stage
20
21.PHONY: publish-prod
22publish-prod:
23 docker tag $(IMAGE):stage $(IMAGE):latest
24 docker push $(IMAGE):latest
25
26.PHONY: clean
27clean:
28 rm -rf docker || true
29 rm $(BINARY) || true
30
31.PHONY: run-web
32run-web: clean $(BINARY)
33 test -n "$(VAULT_ROLE_ID)" # Caller must export VAULT_ROLE_ID
34 test -n "$(VAULT_SECRET_ID)" # Caller must also export VAULT_SECRET_ID
35 VAULT_ADDR="$(VAULT_ADDR)" \
36 ./$(BINARY) --debug \
37 --mongodb-uri="$(MONGODB_URL)" \
38 --mongodb-vault-path="$(MONGODB_VAULT_PATH)" \
39 --github-oauth-vault-path="$(GITHUB_VAULT_PATH)" \
40 web
diff --git a/app/config.go b/app/config.go
new file mode 100644
index 0000000..6565863
--- /dev/null
+++ b/app/config.go
@@ -0,0 +1,70 @@
1package app
2
3import (
4 "log"
5 "time"
6
7 "code.crute.us/mcrute/golib/cli"
8 "code.crute.us/mcrute/golib/vault"
9 "github.com/spf13/cobra"
10)
11
12type GitHubOauthCreds struct {
13 ClientId string `mapstructure:"client-id"`
14 ClientSecret string `mapstructure:"client-secret"`
15}
16
17type Config struct {
18 Bind []string
19 BindTLS []string
20 Debug bool
21 TemplateGlob string
22 TemplatePath string
23 MongoDbUri string
24 MongodbVaultPath string
25 LogFile string
26 TLSCacheDir string
27 TrustedIPRanges []string
28 ManagementIPRanges []string
29 Hostnames []string
30 DisableBackgroundJobs bool
31 RateLimit time.Duration
32 RateLimitBurst int
33 IssuerEndpoint string
34 JWTAudience string
35 AuthCookieDuration time.Duration
36 GitHubOauthCreds *GitHubOauthCreds
37}
38
39func NewConfigFromCmd(cmd *cobra.Command) Config {
40 f := cli.TolerantPflagSet{cmd.Flags()}
41
42 var githubOauth GitHubOauthCreds
43 oauthPath := f.MayGetString("github-oauth-vault-path")
44 err := vault.GetVaultKeyStruct(oauthPath, &githubOauth)
45 if err != nil {
46 log.Fatalf("Error getting %s from vault: %w", oauthPath, err)
47 }
48
49 return Config{
50 Bind: f.MayGetStringSlice("bind"),
51 BindTLS: f.MayGetStringSlice("bind-tls"),
52 Debug: f.MayGetBool("debug"),
53 TemplateGlob: f.MayGetString("template-glob"),
54 TemplatePath: f.MayGetString("template-path"),
55 MongoDbUri: f.MayGetString("mongodb-uri"),
56 MongodbVaultPath: f.MayGetString("mongodb-vault-path"),
57 DisableBackgroundJobs: f.MayGetBool("disable-bg-jobs"),
58 TrustedIPRanges: f.MayGetStringSlice("trusted-ip-ranges"),
59 ManagementIPRanges: f.MayGetStringSlice("management-ip-ranges"),
60 Hostnames: f.MayGetStringSlice("hostname"),
61 LogFile: f.MayGetString("log-file"),
62 TLSCacheDir: f.MayGetString("tls-cache-dir"),
63 RateLimit: f.MayGetDuration("rate-limit"),
64 RateLimitBurst: f.MayGetInt("rate-limit-burst"),
65 IssuerEndpoint: f.MayGetString("issuer-endpoint"),
66 JWTAudience: f.MayGetString("jwt-audience"),
67 AuthCookieDuration: f.MayGetDuration("auth-cookie-duration"),
68 GitHubOauthCreds: &githubOauth,
69 }
70}
diff --git a/app/controllers/api.go b/app/controllers/api.go
new file mode 100644
index 0000000..7beaa4c
--- /dev/null
+++ b/app/controllers/api.go
@@ -0,0 +1,6 @@
1package controllers
2
3const (
4 contentTypeV1 = "application/vnd.broker.v1+json" // Original type
5 contentTypeV2 = "application/vnd.broker.v2+json" // Start of migration to multi-cloud
6)
diff --git a/app/controllers/api_account_list.go b/app/controllers/api_account_list.go
new file mode 100644
index 0000000..f69db6a
--- /dev/null
+++ b/app/controllers/api_account_list.go
@@ -0,0 +1,109 @@
1package controllers
2
3import (
4 "context"
5 "net/http"
6
7 "code.crute.us/mcrute/cloud-identity-broker/app/middleware"
8 "code.crute.us/mcrute/cloud-identity-broker/app/models"
9
10 glecho "code.crute.us/mcrute/golib/echo"
11 "code.crute.us/mcrute/golib/echo/controller"
12 "github.com/labstack/echo/v4"
13)
14
15type jsonAccount struct {
16 Vendor string `json:"vendor,omitempty"`
17 AccountNumber int `json:"account_number"`
18 ShortName string `json:"short_name"`
19 Name string `json:"name"`
20 ConsoleUrl string `json:"get_console_url,omitempty"`
21 ConsoleRedirectUrl string `json:"console_redirect_url,omitempty"`
22 CredentialsUrl string `json:"credentials_url"`
23 GlobalCredentialsUrl string `json:"global_credential_url,omitempty"`
24}
25
26func jsonAccountFromAccount(c echo.Context, a *models.Account) *jsonAccount {
27 return &jsonAccount{
28 AccountNumber: a.AccountNumber,
29 ShortName: a.ShortName,
30 Name: a.Name,
31 ConsoleUrl: glecho.URLFor(c, "/api/account", a.ShortName, "console").String(),
32 ConsoleRedirectUrl: glecho.URLFor(c, "/api/account", a.ShortName, "console").Query("redirect", "1").String(),
33 CredentialsUrl: glecho.URLFor(c, "/api/account", a.ShortName, "credentials").String(),
34 GlobalCredentialsUrl: glecho.URLFor(c, "/api/account", a.ShortName, "credentials/global").String(),
35 }
36}
37
38type APIAccountListHandler struct {
39 store models.AccountStore
40}
41
42func NewAPIAccountListHandler(s models.AccountStore) echo.HandlerFunc {
43 al := &APIAccountListHandler{store: s}
44 h := &controller.ContentTypeNegotiatingHandler{
45 DefaultHandler: al.HandleV1,
46 Handlers: map[string]echo.HandlerFunc{
47 contentTypeV1: al.HandleV1,
48 contentTypeV2: al.HandleV2,
49 },
50 }
51 return h.Handle
52}
53
54// getAccountList returns the account list. This does the same work that
55// GetContext would do for most AWSAPI handlers but is a little different
56// because it deals with lists of accounts.
57//
58// Authorization of the account is handled within the store. The store will not
59// return accounts for which the user does not have access.
60func (h *APIAccountListHandler) getAccountList(c echo.Context) ([]*models.Account, error) {
61 principal, err := middleware.GetAuthorizedPrincipal(c)
62 if err != nil {
63 return nil, echo.ErrUnauthorized
64 }
65
66 accounts, err := h.store.ListForUser(context.Background(), principal)
67 if err != nil {
68 c.Logger().Errorf("Unable to load account list: %w", err)
69 return nil, echo.ErrInternalServerError
70 }
71
72 return accounts, nil
73}
74
75// HandleV1 returns a list of JSON account objects
76func (h *APIAccountListHandler) HandleV1(c echo.Context) error {
77 accounts, err := h.getAccountList(c)
78 if err != nil {
79 return err
80 }
81
82 out := []*jsonAccount{}
83 for _, a := range accounts {
84 ja := jsonAccountFromAccount(c, a)
85 ja.Vendor = "aws"
86 out = append(out, ja)
87 }
88
89 return c.JSON(http.StatusOK, out)
90}
91
92// HandleV2 returns a map of lists of account objects. the key to the map is
93// the short name of the cloud provider.
94func (h *APIAccountListHandler) HandleV2(c echo.Context) error {
95 accounts, err := h.getAccountList(c)
96 if err != nil {
97 return err
98 }
99
100 out := map[string][]*jsonAccount{
101 "aws": []*jsonAccount{},
102 }
103 for _, a := range accounts {
104 ja := jsonAccountFromAccount(c, a)
105 out["aws"] = append(out["aws"], ja)
106 }
107
108 return c.JSON(http.StatusOK, out)
109}
diff --git a/app/controllers/api_console_redirect.go b/app/controllers/api_console_redirect.go
new file mode 100644
index 0000000..701bbf3
--- /dev/null
+++ b/app/controllers/api_console_redirect.go
@@ -0,0 +1,63 @@
1package controllers
2
3import (
4 "net/http"
5
6 "code.crute.us/mcrute/golib/echo/controller"
7 "github.com/labstack/echo/v4"
8 "github.com/prometheus/client_golang/prometheus"
9 "github.com/prometheus/client_golang/prometheus/promauto"
10)
11
12var consoleAllowed = promauto.NewCounterVec(prometheus.CounterOpts{
13 Namespace: "aws_access", // Legacy Namespace
14 Name: "broker_console_access_total",
15 Help: "Total number of console logins allowed by broker",
16}, []string{"account"})
17
18type jsonConsoleUrl struct {
19 ConsoleURL string `json:"console_url"`
20}
21
22type APIConsoleRedirectHandler struct {
23 FederationIssuerEndpoint string
24 *AWSAPI
25}
26
27func NewAPIConsoleRedirectHandler(a *AWSAPI, fe string) echo.HandlerFunc {
28 al := &APIConsoleRedirectHandler{fe, a}
29 h := &controller.ContentTypeNegotiatingHandler{
30 DefaultHandler: al.Handle,
31 Handlers: map[string]echo.HandlerFunc{
32 contentTypeV1: al.Handle,
33 },
34 }
35 return h.Handle
36}
37
38func (h *APIConsoleRedirectHandler) Handle(c echo.Context) error {
39 rc, err := h.GetContext(c) // Does all authorization checks
40 if err != nil {
41 return err
42 }
43
44 u, err := rc.AWS.GetFederationURL(rc.Principal.Username, h.FederationIssuerEndpoint)
45 if err != nil {
46 c.Logger().Errorf("Error fetching console URL: %w", err)
47 return echo.ErrBadRequest
48 }
49
50 c.Logger().Infof(
51 "Allowing '%s' to access account console '%s'",
52 rc.Principal.Username, rc.Account.Name,
53 )
54 consoleAllowed.With(prometheus.Labels{
55 "account": rc.Account.ShortName,
56 }).Inc()
57
58 if c.QueryParam("redirect") == "1" {
59 return c.Redirect(http.StatusFound, u)
60 } else {
61 return c.JSON(http.StatusOK, &jsonConsoleUrl{u})
62 }
63}
diff --git a/app/controllers/api_credentials.go b/app/controllers/api_credentials.go
new file mode 100644
index 0000000..1cefc07
--- /dev/null
+++ b/app/controllers/api_credentials.go
@@ -0,0 +1,76 @@
1package controllers
2
3import (
4 "net/http"
5 "time"
6
7 "code.crute.us/mcrute/cloud-identity-broker/cloud/aws"
8
9 "code.crute.us/mcrute/golib/echo/controller"
10 "github.com/labstack/echo/v4"
11 "github.com/prometheus/client_golang/prometheus"
12 "github.com/prometheus/client_golang/prometheus/promauto"
13)
14
15var credsAllowed = promauto.NewCounterVec(prometheus.CounterOpts{
16 Namespace: "aws_access", // Legacy namespace
17 Name: "broker_cred_access_total",
18 Help: "Total number of credential accesses allowed by broker",
19}, []string{"account", "region"})
20
21type jsonCredential struct {
22 AccessKeyId *string `json:"access_key"`
23 SecretAccessKey *string `json:"secret_key"`
24 SessionToken *string `json:"session_token"`
25 Expiration *time.Time `json:"expiration"`
26}
27
28type APICredentialsHandler struct {
29 *AWSAPI
30}
31
32func NewAPICredentialsHandler(a *AWSAPI) echo.HandlerFunc {
33 al := &APICredentialsHandler{a}
34 h := &controller.ContentTypeNegotiatingHandler{
35 DefaultHandler: al.Handle,
36 Handlers: map[string]echo.HandlerFunc{
37 contentTypeV1: al.Handle,
38 },
39 }
40 return h.Handle
41}
42
43func (h *APICredentialsHandler) Handle(c echo.Context) error {
44 rc, err := h.GetContext(c) // Does authorization checks
45 if err != nil {
46 return err
47 }
48
49 region := c.Param("region")
50 creds, err := rc.AWS.AssumeRole(rc.Principal.Username, &region)
51 if err != nil {
52 if aws.IsRegionNotExist(err) {
53 return echo.NotFoundHandler(c)
54 }
55 c.Logger().Errorf("Error retrieving credentials: %w", err)
56 return echo.ErrInternalServerError
57 }
58
59 c.Logger().Infof(
60 "Allowing '%s' to access account credential '%s'",
61 rc.Principal.Username, rc.Account.Name,
62 )
63 credsAllowed.With(prometheus.Labels{
64 "account": rc.Account.ShortName,
65 "region": region,
66 }).Inc()
67
68 c.Response().Header().Set("Expires", creds.Expiration.Add(-5*time.Minute).Format(time.RFC1123))
69
70 return c.JSON(http.StatusOK, &jsonCredential{
71 AccessKeyId: creds.AccessKeyId,
72 SecretAccessKey: creds.SecretAccessKey,
73 SessionToken: creds.SessionToken,
74 Expiration: creds.Expiration,
75 })
76}
diff --git a/app/controllers/api_region_list.go b/app/controllers/api_region_list.go
new file mode 100644
index 0000000..5bd1f7e
--- /dev/null
+++ b/app/controllers/api_region_list.go
@@ -0,0 +1,61 @@
1package controllers
2
3import (
4 "net/http"
5
6 glecho "code.crute.us/mcrute/golib/echo"
7 "code.crute.us/mcrute/golib/echo/controller"
8 "github.com/labstack/echo/v4"
9)
10
11type jsonRegion struct {
12 Name string `json:"name"`
13 Enabled bool `json:"enabled"`
14 Default bool `json:"default"`
15 CredentialsURL string `json:"credentials_url,omitempty"`
16}
17
18type APIRegionListHandler struct {
19 *AWSAPI
20}
21
22func NewAPIRegionListHandler(a *AWSAPI) echo.HandlerFunc {
23 al := &APIRegionListHandler{a}
24 h := &controller.ContentTypeNegotiatingHandler{
25 DefaultHandler: al.Handle,
26 Handlers: map[string]echo.HandlerFunc{
27 contentTypeV1: al.Handle,
28 },
29 }
30 return h.Handle
31}
32
33func (h *APIRegionListHandler) Handle(c echo.Context) error {
34 rc, err := h.GetContext(c) // Does authorization checks
35 if err != nil {
36 return err
37 }
38
39 regions, err := rc.AWS.GetRegionList()
40 if err != nil {
41 c.Logger().Errorf("Failed to load region list: %w", err)
42 return echo.ErrInternalServerError
43 }
44
45 out := make([]*jsonRegion, len(regions))
46
47 for i, r := range regions {
48 out[i] = &jsonRegion{
49 Name: r.Name,
50 Enabled: r.Enabled,
51 Default: rc.Account.DefaultRegion == r.Name,
52 }
53 if r.Enabled {
54 out[i].CredentialsURL = glecho.URLFor(c,
55 "/api/account", rc.Account.ShortName, "credentials", r.Name,
56 ).String()
57 }
58 }
59
60 return c.JSON(http.StatusOK, out)
61}
diff --git a/app/controllers/aws.go b/app/controllers/aws.go
new file mode 100644
index 0000000..5b1765d
--- /dev/null
+++ b/app/controllers/aws.go
@@ -0,0 +1,52 @@
1package controllers
2
3import (
4 "context"
5
6 "code.crute.us/mcrute/cloud-identity-broker/app/middleware"
7 "code.crute.us/mcrute/cloud-identity-broker/app/models"
8 "code.crute.us/mcrute/cloud-identity-broker/cloud/aws"
9
10 "github.com/labstack/echo/v4"
11)
12
13type requestContext struct {
14 Account *models.Account
15 Principal *models.User
16 AWS aws.AWSClient
17}
18
19// AWSAPI is a capability that all handlers talking to the AWS APIs should use.
20// This capability does common permission checks and populates a request
21// context with user, account, and AWS API information.
22type AWSAPI struct {
23 Store models.AccountStore
24}
25
26// GetContext checks that the user is authenticated and is authorized to access
27// the requested AWS account. This should be the very first call in any handler
28// that will eventually call the AWS APIs. Errors returned from this method are
29// echo responses and can be returned directly to the client.
30func (h *AWSAPI) GetContext(c echo.Context) (*requestContext, error) {
31 principal, err := middleware.GetAuthorizedPrincipal(c)
32 if err != nil {
33 return nil, echo.ErrUnauthorized
34 }
35
36 account, err := h.Store.GetForUser(context.Background(), c.Param("account"), principal)
37 if err != nil {
38 return nil, echo.NotFoundHandler(c)
39 }
40
41 ac, err := aws.NewAWSClientFromAccount(account)
42 if err != nil {
43 c.Logger().Errorf("Error building AWS client: %w", err)
44 return nil, echo.ErrInternalServerError
45 }
46
47 return &requestContext{
48 Account: account,
49 Principal: principal,
50 AWS: ac,
51 }, nil
52}
diff --git a/app/controllers/basic.go b/app/controllers/basic.go
new file mode 100644
index 0000000..eff97e1
--- /dev/null
+++ b/app/controllers/basic.go
@@ -0,0 +1,17 @@
1package controllers
2
3import (
4 "net/http"
5
6 glecho "code.crute.us/mcrute/golib/echo"
7 "github.com/labstack/echo/v4"
8)
9
10func IndexHandler(c echo.Context) error {
11 return c.Render(http.StatusOK, "index.tpl", nil)
12}
13
14func LogoutHandler(c echo.Context) error {
15 glecho.DeleteAllCookies(c)
16 return c.Redirect(http.StatusFound, "/")
17}
diff --git a/app/middleware/auth.go b/app/middleware/auth.go
new file mode 100644
index 0000000..167d261
--- /dev/null
+++ b/app/middleware/auth.go
@@ -0,0 +1,212 @@
1package middleware
2
3import (
4 "context"
5 "fmt"
6 "net/http"
7 "strings"
8 "time"
9
10 "code.crute.us/mcrute/cloud-identity-broker/app/models"
11 "code.crute.us/mcrute/cloud-identity-broker/auth"
12 "code.crute.us/mcrute/cloud-identity-broker/auth/github"
13
14 "github.com/labstack/echo/v4"
15)
16
17// canRegisterUrls is an interface that identifies what about an HTTP router is
18// needed by this middleware. This mainly exists to work around the fact that
19// our server is actually a golib.EchoWrapper and not an echo.Echo.
20type canRegisterUrls interface {
21 GET(string, echo.HandlerFunc, ...echo.MiddlewareFunc) *echo.Route
22}
23
24const (
25 authPrincipalContextKey = "broker.AuthorizedPrincipal"
26 gitHubTokenCookie = "github-token"
27 gitHubStateCookie = "github-state"
28 oauthReturnUrl = "/github-auth"
29)
30
31// GetAuthorizedPrincipal returns the user principal object from the request
32// context and casts it correctly. Will return error if there is no principal
33// or if the principal is of the incorrect type.
34//
35// Note that use of this function implies that AuthenticationMiddleware is used
36// somewhere in the stack before the handler calling this function is
37// dispatched.
38func GetAuthorizedPrincipal(c echo.Context) (*models.User, error) {
39 rp := c.Get(authPrincipalContextKey)
40 if rp == nil {
41 return nil, fmt.Errorf("No principal set in request")
42 }
43 principal, ok := rp.(*models.User)
44 if !ok {
45 return nil, fmt.Errorf("Principal in request is not of User type")
46 }
47 return principal, nil
48}
49
50type AuthenticationMiddleware struct {
51 Store models.UserStore
52 JWTManager *auth.JWTManager
53 GitHub *github.GitHubAuthenticator
54 CookieDuration time.Duration
55}
56
57func (m *AuthenticationMiddleware) redirectToGitHubAuth(c echo.Context) error {
58 redir, state := m.GitHub.GetAuthRedirect()
59
60 c.SetCookie(&http.Cookie{
61 Name: gitHubStateCookie,
62 Value: state,
63 Path: "/",
64 Secure: true,
65 HttpOnly: true,
66 SameSite: http.SameSiteStrictMode,
67 })
68
69 return c.Redirect(http.StatusFound, redir)
70}
71
72// RegisterUrls registers the URLs required by this middleware and handler with an echo instance.
73//
74// This is here instead of in the web main because these paths are encoded in
75// the configuration for the GitHub application so changing them requires
76// addition changes to that configuration.
77func (m *AuthenticationMiddleware) RegisterUrls(e canRegisterUrls) {
78 e.GET(oauthReturnUrl, m.HandleCompleteLogin)
79}
80
81// Middleware does user authentication based on either an X-API-Key header,
82// Authorization header, or GitHub cookie depending on how the request is
83// phrased.
84//
85// If the request has either an X-API-Key or an Authorization Bearer header
86// then that must pass validation with the downstream validation logic.
87// Failures through this path are hard failures and the only way to re-try them
88// is to authenticate with a new token. The underlying assumption is that only
89// programmatic access goes through this path so redirecting to interactive
90// authentication is pointless.
91//
92// In the absence of those headers it's assumed that the user is interactive
93// and their auth cookie will be read and validated (by the exact same logic
94// that an API key is validated, they're the same format) but the failure case
95// here will redirect the user to GitHub for interactive auth.
96//
97// X-API-Key should be considered deprecated and the Authorization header with
98// a type of Bearer should be used instead. This is more in-line with Oauth 2
99// style authentication. However, for now this middleware continues to support
100// X-API-Key for to not break legacy API clients.
101func (m *AuthenticationMiddleware) Middleware(next echo.HandlerFunc) echo.HandlerFunc {
102 return func(c echo.Context) error {
103 token := c.Request().Header.Get("X-API-Key")
104 if token == "" {
105 tp := strings.Split(c.Request().Header.Get(echo.HeaderAuthorization), " ")
106 if len(tp) == 2 && tp[0] == "Bearer" {
107 token = tp[1]
108 }
109 }
110
111 // If an API key is specified this is a success or failure path. There
112 // is no option to authenticate to GitHub as would an interactive user.
113 if token != "" {
114 u, err := m.JWTManager.Validate(token)
115 if err != nil {
116 c.Logger().Debugf("Error validating JWT: %w", err)
117 return echo.ErrUnauthorized
118 }
119 c.Set(authPrincipalContextKey, u)
120 return next(c)
121 }
122
123 // Kick them to GitHub auth if they have no auth cookie
124 authCookie, err := c.Cookie(gitHubTokenCookie)
125 if err != nil {
126 return m.redirectToGitHubAuth(c)
127 }
128
129 // If they fail the check them bounce them through logout to remove
130 // their existing cookies which should then bounce them back through
131 // GitHub auth, which will eventually land them back here.
132 u, err := m.JWTManager.Validate(authCookie.Value)
133 if err != nil {
134 c.Logger().Debugf("Error validating JWT: %w", err)
135 return c.Redirect(http.StatusFound, "/logout")
136 }
137
138 c.Set(authPrincipalContextKey, u)
139 return next(c)
140 }
141}
142
143// HandleCompleteLogin handles the Oauth 2 code flow. It receives the auth code
144// and uses that to retrieve the auth token. This sets the user's auth cookie
145// to a authenticated JWT.
146//
147// This is redirected-to by the Oauth authorization server and should never be
148// hit directly by a user or script.
149func (m *AuthenticationMiddleware) HandleCompleteLogin(c echo.Context) error {
150 ctx := context.Background()
151
152 code, state := c.QueryParam("code"), c.QueryParam("state")
153 if code == "" || state == "" {
154 return echo.ErrBadRequest
155 }
156
157 ghState, err := c.Cookie(gitHubStateCookie)
158 if err != nil || ghState.Value == "" {
159 return echo.ErrBadRequest
160 }
161
162 if ghState.Value != state {
163 return echo.ErrBadRequest
164 }
165
166 token, err := m.GitHub.GetTokens(code)
167 if err != nil {
168 return echo.ErrBadRequest
169 }
170
171 user, err := m.GitHub.GetUsernameWithToken(token.AccessToken)
172 if err != nil {
173 c.Logger().Debugf("Error getting GitHub username with token: %w", err)
174 return echo.ErrUnauthorized
175 }
176
177 dbUser, err := m.Store.Get(ctx, user)
178 if err != nil {
179 c.Logger().Errorf("GitHub user %s does not have access to app", user)
180 return echo.ErrUnauthorized
181 }
182
183 jwt, sk, err := m.JWTManager.CreateForUser(dbUser)
184 if err != nil {
185 return echo.ErrInternalServerError
186 }
187
188 dbUser.AddKey(sk)
189 dbUser.GCKeys() // This is a convenient place to do it
190
191 dbUser.AddToken(&models.AuthToken{
192 Kind: "github",
193 Token: token.AccessToken,
194 RefreshToken: token.RefreshToken,
195 })
196
197 if err := m.Store.Put(ctx, dbUser); err != nil {
198 return echo.ErrInternalServerError
199 }
200
201 c.SetCookie(&http.Cookie{
202 Name: gitHubTokenCookie,
203 Value: jwt,
204 Path: "/",
205 MaxAge: int(m.CookieDuration.Seconds()),
206 Secure: true,
207 HttpOnly: true,
208 SameSite: http.SameSiteStrictMode,
209 })
210
211 return c.Redirect(http.StatusFound, "/")
212}
diff --git a/app/models/account.go b/app/models/account.go
new file mode 100644
index 0000000..0ae1821
--- /dev/null
+++ b/app/models/account.go
@@ -0,0 +1,115 @@
1package models
2
3import (
4 "context"
5 "fmt"
6 "time"
7
8 "code.crute.us/mcrute/golib/db/mongodb"
9)
10
11const accountCol = "accounts"
12
13type AccountStore interface {
14 List(context.Context) ([]*Account, error)
15 ListForUser(context.Context, *User) ([]*Account, error)
16 Get(context.Context, string) (*Account, error) // Error on not found
17 GetForUser(context.Context, string, *User) (*Account, error) // Error on not found
18 Put(context.Context, *Account) error
19 Delete(context.Context, *Account) error
20}
21
22type Account struct {
23 ShortName string `bson:"_id"`
24 AccountType string
25 AccountNumber int
26 Name string
27 ConsoleSessionDuration time.Duration
28 VaultMaterial string
29 DefaultRegion string
30 Users []string
31}
32
33func (a *Account) ConsoleSessionDurationSecs() int64 {
34 return int64(a.ConsoleSessionDuration.Seconds())
35}
36
37func (a *Account) CanAccess(u *User) bool {
38 if u.IsAdmin {
39 return true
40 }
41 // Linear search should be fine for now, these lists are pretty small
42 for _, n := range a.Users {
43 if n == u.Username {
44 return true
45 }
46 }
47 return false
48}
49
50type MongoDbAccountStore struct {
51 Db *mongodb.Mongo
52}
53
54// List returns all accounts in the system.
55func (s *MongoDbAccountStore) List(ctx context.Context) ([]*Account, error) {
56 var out []*Account
57 if err := s.Db.FindAll(ctx, accountCol, &out); err != nil {
58 return nil, err
59 }
60 return out, nil
61}
62
63// ListForUser returns all accounts for which the user has access. This is the
64// authorized version of List.
65//
66// Note this does not handle the case where a user is an admin but not
67// explicitly listed in the allowed users list for an account. For that case
68// just use List directly.
69func (s *MongoDbAccountStore) ListForUser(ctx context.Context, u *User) ([]*Account, error) {
70 var out []*Account
71 filter := mongodb.AnyInTopLevelArray("Users", u.Username)
72 if err := s.Db.FindAllByFilter(ctx, accountCol, filter, &out); err != nil {
73 return nil, err
74 }
75 return out, nil
76}
77
78func (s *MongoDbAccountStore) Get(ctx context.Context, id string) (*Account, error) {
79 var a Account
80 if err := s.Db.FindOneById(ctx, accountCol, id, &a); err != nil {
81 return nil, err
82 }
83 return &a, nil
84}
85
86// GetForUser returns an account if the user has access to this account,
87// otherwise it returns an error. This is the authorized version of Get.
88func (s *MongoDbAccountStore) GetForUser(ctx context.Context, id string, u *User) (*Account, error) {
89 a, err := s.Get(ctx, id)
90 if err != nil {
91 return nil, err
92 }
93
94 if !a.CanAccess(u) {
95 return nil, fmt.Errorf("User does not have access to account")
96 }
97
98 return a, nil
99}
100
101func (s *MongoDbAccountStore) Put(ctx context.Context, a *Account) error {
102 if err := s.Db.ReplaceOneById(ctx, accountCol, a.ShortName, a); err != nil {
103 return err
104 }
105 return nil
106}
107
108func (s *MongoDbAccountStore) Delete(ctx context.Context, a *Account) error {
109 if err := s.Db.DeleteOneById(ctx, accountCol, a.ShortName); err != nil {
110 return err
111 }
112 return nil
113}
114
115var _ AccountStore = (*MongoDbAccountStore)(nil)
diff --git a/app/models/session_key.go b/app/models/session_key.go
new file mode 100644
index 0000000..64ac7e0
--- /dev/null
+++ b/app/models/session_key.go
@@ -0,0 +1,202 @@
1package models
2
3import (
4 "crypto"
5 "crypto/ecdsa"
6 "crypto/elliptic"
7 "crypto/rand"
8 "crypto/x509"
9 "encoding/base64"
10 "encoding/hex"
11 "fmt"
12 "time"
13
14 "go.mongodb.org/mongo-driver/bson"
15)
16
17// SessionKey represents a public and sometimes private key-pair for a user
18// that will be stored on the user's record in the user store. These keys are
19// used for signing authentication JWTs.
20//
21// This object is designed to be serialized to BSON. Other serializations can
22// be added in the future as needed.
23//
24// There are two flavors of this record. A record with a private key (which
25// implies a public key) is a key that the service generated and is used by the
26// service to sign JWTs for the user. The private key is never given to the
27// user. The private key is only used in the CreateToken flow, never the Verify
28// flow. Currently (as of Nov 2021) the application sets a near-future NotAfter
29// date and these get garbage collected. It might be nice to re-use them in the
30// future for a while but it's not all that important.
31//
32// The other flavor of this key will have a public key but no private key.
33// These are service keys. Service keys are given to programmatic actors that
34// need to be able to mint their own JWTs for authentication to the service.
35// For these keys the client will construct their own JWT and sign it with the
36// private key and the service will validate the signature with the public key.
37// These keys (as of Nov 2021) do not expire, though they can be revoked.
38type SessionKey struct {
39 KeyId string
40 Description string
41 Revoked *time.Time
42 NotAfter *time.Time
43 NotBefore *time.Time
44 PublicKey crypto.PublicKey
45 PrivateKey *ecdsa.PrivateKey
46}
47
48func GenerateSessionKey(ttl time.Duration) (*SessionKey, error) {
49 pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
50 if err != nil {
51 return nil, err
52 }
53
54 key := make([]byte, 8)
55 if _, err := rand.Read(key); err != nil {
56 return nil, err
57 }
58
59 now := time.Now()
60 notAfter := now.Add(ttl)
61
62 return &SessionKey{
63 KeyId: hex.EncodeToString(key),
64 Revoked: nil,
65 NotAfter: &notAfter,
66 NotBefore: &now,
67 PublicKey: pk.Public(),
68 PrivateKey: pk,
69 }, nil
70}
71
72// IsGarbage checks to determine if a key is garbage that should be collected.
73// The definition of garbage is similar to the inversion of the definition of
74// vaild but revoked keys are not considered to be garbage since they may be
75// useful for auditing later. Also keys that are not yet valid are not garbage.
76func (s *SessionKey) IsGarbage() bool {
77 if s.Revoked != nil {
78 return false
79 }
80
81 if s.NotBefore != nil && s.NotBefore.Before(time.Now()) {
82 return false
83 }
84
85 if s.NotAfter != nil && s.NotAfter.After(time.Now()) {
86 return true
87 }
88
89 return false
90}
91
92// IsValid checks the various dates in the SessionKey to verify that they are
93// valid and in-range for use. This should be called before trusting this key
94// for any use.
95func (s *SessionKey) IsValid() bool {
96 if s.Revoked != nil {
97 return false
98 }
99
100 if s.NotBefore != nil && s.NotBefore.Before(time.Now()) {
101 return false
102 }
103
104 if s.NotAfter != nil && s.NotAfter.After(time.Now()) {
105 return false
106 }
107
108 return true
109}
110
111func (s *SessionKey) MarshalBSON() ([]byte, error) {
112 var err error
113 var pub, priv []byte
114
115 if s.PrivateKey != nil {
116 priv, err = x509.MarshalECPrivateKey(s.PrivateKey)
117 if err != nil {
118 return nil, err
119 }
120 }
121
122 // If there's a private key and a public key set then just save the private
123 // key. The private key already contains a copy of the public key.
124 if s.PublicKey != nil && s.PrivateKey == nil {
125 pub, err = x509.MarshalPKIXPublicKey(s.PublicKey)
126 if err != nil {
127 return nil, err
128 }
129 }
130
131 return bson.Marshal(struct {
132 KeyId string
133 Revoked *time.Time
134 NotAfter *time.Time
135 NotBefore *time.Time
136 PublicKey string
137 PrivateKey string
138 }{
139 s.KeyId,
140 s.Revoked, s.NotAfter, s.NotBefore,
141 base64.StdEncoding.EncodeToString(pub),
142 base64.StdEncoding.EncodeToString(priv),
143 })
144}
145
146func (s *SessionKey) UnmarshalBSON(d []byte) error {
147 v := struct {
148 KeyId string
149 Revoked *time.Time
150 NotAfter *time.Time
151 NotBefore *time.Time
152 PublicKey string
153 PrivateKey string
154 }{}
155 if err := bson.Unmarshal(d, &v); err != nil {
156 return err
157 }
158
159 s.KeyId = v.KeyId
160 s.Revoked = v.Revoked
161 s.NotAfter = v.NotAfter
162 s.NotBefore = v.NotBefore
163
164 if v.PrivateKey != "" {
165 privb, err := base64.StdEncoding.DecodeString(v.PrivateKey)
166 if err != nil {
167 return err
168 }
169
170 priv, err := x509.ParseECPrivateKey(privb)
171 if err != nil {
172 return err
173 }
174
175 s.PrivateKey = priv
176 s.PublicKey = priv.Public()
177 }
178
179 // If there was a private key then the public key was already set by
180 // decoding that private key. No need to do this a second time (also it's
181 // rather unlikely that both would be set).
182 if v.PublicKey != "" && s.PublicKey == nil {
183 pubb, err := base64.StdEncoding.DecodeString(v.PublicKey)
184 if err != nil {
185 return err
186 }
187
188 pubp, err := x509.ParsePKIXPublicKey(pubb)
189 if err != nil {
190 return err
191 }
192
193 pub, ok := pubp.(*ecdsa.PublicKey)
194 if !ok {
195 return fmt.Errorf("Failed to convert public key to *ecdsa.PublicKey")
196 }
197
198 s.PublicKey = pub
199 }
200
201 return nil
202}
diff --git a/app/models/user.go b/app/models/user.go
new file mode 100644
index 0000000..0cbd92d
--- /dev/null
+++ b/app/models/user.go
@@ -0,0 +1,99 @@
1package models
2
3import (
4 "context"
5
6 "code.crute.us/mcrute/golib/db/mongodb"
7)
8
9const userCol = "users"
10
11type UserStore interface {
12 List(context.Context) ([]*User, error)
13 Get(context.Context, string) (*User, error) // Error on not found
14 Put(context.Context, *User) error
15 Delete(context.Context, *User) error
16}
17
18type AuthToken struct {
19 Kind string
20 Token string
21 RefreshToken string
22}
23
24type User struct {
25 Username string `bson:"_id"`
26 IsAdmin bool
27 IsService bool
28 Keys map[string]*SessionKey // kid -> key
29 AuthTokens map[string]*AuthToken // kind -> token
30}
31
32// GCKeys garbage collects keys that are no longer valid
33func (u *User) GCKeys() {
34 for k, v := range u.Keys {
35 if v.IsGarbage() {
36 delete(u.Keys, k)
37 }
38 }
39}
40
41// GetKey returns a key for a key ID. It will only return valid keys.
42func (u *User) GetKey(kid string) *SessionKey {
43 if u.Keys != nil {
44 if k := u.Keys[kid]; k != nil && k.IsValid() {
45 return k
46 }
47 }
48 return nil
49}
50
51func (u *User) AddKey(k *SessionKey) {
52 if u.Keys == nil {
53 u.Keys = map[string]*SessionKey{}
54 }
55 u.Keys[k.KeyId] = k
56}
57
58func (u *User) AddToken(t *AuthToken) {
59 if u.AuthTokens == nil {
60 u.AuthTokens = map[string]*AuthToken{}
61 }
62 u.AuthTokens[t.Kind] = t
63}
64
65type MongoDbUserStore struct {
66 Db *mongodb.Mongo
67}
68
69func (s *MongoDbUserStore) List(ctx context.Context) ([]*User, error) {
70 var out []*User
71 if err := s.Db.FindAll(ctx, userCol, &out); err != nil {
72 return nil, err
73 }
74 return out, nil
75}
76
77func (s *MongoDbUserStore) Get(ctx context.Context, username string) (*User, error) {
78 var u User
79 if err := s.Db.FindOneById(ctx, userCol, username, &u); err != nil {
80 return nil, err
81 }
82 return &u, nil
83}
84
85func (s *MongoDbUserStore) Put(ctx context.Context, u *User) error {
86 if err := s.Db.ReplaceOneById(ctx, userCol, u.Username, u); err != nil {
87 return err
88 }
89 return nil
90}
91
92func (s *MongoDbUserStore) Delete(ctx context.Context, u *User) error {
93 if err := s.Db.DeleteOneById(ctx, userCol, u.Username); err != nil {
94 return err
95 }
96 return nil
97}
98
99var _ UserStore = (*MongoDbUserStore)(nil)
diff --git a/auth/github/github.go b/auth/github/github.go
new file mode 100644
index 0000000..b403296
--- /dev/null
+++ b/auth/github/github.go
@@ -0,0 +1,127 @@
1package github
2
3import (
4 "crypto/rand"
5 "encoding/base64"
6 "encoding/json"
7 "fmt"
8 "io/ioutil"
9 "net/http"
10 "net/url"
11 "strings"
12)
13
14type apiLoginResponse struct {
15 Username string `json:"login"`
16}
17
18type GitHubToken struct {
19 AccessToken string `json:"access_token"`
20 RefreshToken string `json:"refresh_token"`
21}
22
23type GitHubAuthenticator struct {
24 ClientId string
25 ClientSecret string
26}
27
28// GetAuthRedirect returns a redirect URL and a state parameter that should be
29// stored in the user's browser to correlate the authentication result and
30// request.
31func (a *GitHubAuthenticator) GetAuthRedirect() (string, string) {
32 sr := make([]byte, 32)
33 if _, err := rand.Read(sr); err != nil {
34 panic(err) // This should only happen if no entropy available
35 }
36 random := base64.URLEncoding.EncodeToString(sr)
37
38 return (&url.URL{
39 Scheme: "https",
40 Host: "github.com",
41 Path: "/login/oauth/authorize",
42 RawQuery: url.Values{
43 "client_id": []string{a.ClientId},
44 "scope": []string{"read:user"},
45 "state": []string{random},
46 "allow_signup": []string{"false"},
47 }.Encode(),
48 }).String(), random
49}
50
51// GetTokens returns the GitHub Oauth tokens from the service given a token
52// request code. The returned tokens are used for authentication to the GitHub
53// API.
54//
55// This relies on a GitHub Oauth application on the GitHub side to act as the
56// client for these tokens.
57func (a *GitHubAuthenticator) GetTokens(code string) (*GitHubToken, error) {
58 r, err := (&http.Client{}).Do(&http.Request{
59 Method: http.MethodPost,
60 URL: &url.URL{
61 Scheme: "https",
62 Host: "github.com",
63 Path: "/login/oauth/access_token",
64 },
65 Header: http.Header{
66 "Content-Type": []string{"application/x-www-form-urlencoded"},
67 "Accept": []string{"application/json"},
68 },
69 Body: ioutil.NopCloser(strings.NewReader(url.Values{
70 "client_id": []string{a.ClientId},
71 "client_secret": []string{a.ClientSecret},
72 "code": []string{code},
73 }.Encode())),
74 })
75 if err != nil {
76 return nil, err
77 }
78 defer r.Body.Close()
79
80 body, err := ioutil.ReadAll(r.Body)
81 if err != nil {
82 return nil, err
83 }
84
85 var ret GitHubToken
86 if err = json.Unmarshal(body, &ret); err != nil {
87 return nil, err
88 }
89
90 return &ret, nil
91}
92
93// GetUsernameWithToken returns the authenticated user's GitHub username given
94// an Oauth auth token.
95func (a *GitHubAuthenticator) GetUsernameWithToken(accessToken string) (string, error) {
96 r, err := (&http.Client{}).Do(&http.Request{
97 Method: http.MethodGet,
98 URL: &url.URL{
99 Scheme: "https",
100 Host: "api.github.com",
101 Path: "/user",
102 },
103 Header: http.Header{
104 "Authorization": []string{fmt.Sprintf("token %s", accessToken)},
105 },
106 })
107 if err != nil {
108 return "", err
109 }
110 defer r.Body.Close()
111
112 body, err := ioutil.ReadAll(r.Body)
113 if err != nil {
114 return "", err
115 }
116
117 var data apiLoginResponse
118 if err = json.Unmarshal(body, &data); err != nil {
119 return "", err
120 }
121
122 if data.Username == "" {
123 return "", fmt.Errorf("No user returned in GitHub response")
124 }
125
126 return data.Username, nil
127}
diff --git a/auth/jwt.go b/auth/jwt.go
new file mode 100644
index 0000000..c65cf39
--- /dev/null
+++ b/auth/jwt.go
@@ -0,0 +1,123 @@
1package auth
2
3import (
4 "context"
5 "fmt"
6 "time"
7
8 "code.crute.us/mcrute/cloud-identity-broker/app/models"
9
10 "gopkg.in/square/go-jose.v2"
11 "gopkg.in/square/go-jose.v2/jwt"
12)
13
14const keyIdHeader = jose.HeaderKey("kid")
15
16type JWTManager struct {
17 Store models.UserStore
18 Audience string
19 TokenExpires time.Duration
20}
21
22// Validate performs validate of a JWT for authentication. If the
23// authentication was successful it will return the user model for the
24// authenticated user.
25//
26// JWTs are signed by a per-user key on the service side. These keys may be
27// generated automatically when issuing a token to the user or, in the case of
28// service accounts, they may be generated by the user and validated on the
29// service side with the user's public key.
30func (m *JWTManager) Validate(j string) (*models.User, error) {
31 ctx := context.Background()
32
33 parsed, err := jwt.ParseSigned(j)
34 if err != nil {
35 return nil, err
36 }
37
38 // With a single signature there should be exactly one header. Without that
39 // we can't get the key id which means we won't be able to get the key and
40 // validate the signature.
41 if len(parsed.Headers) != 1 {
42 return nil, fmt.Errorf("Expected exactly 1 JWT header, got %d", len(parsed.Headers))
43 }
44
45 kid := parsed.Headers[0].KeyID
46 if kid == "" {
47 return nil, fmt.Errorf("No key ID in token header")
48 }
49
50 // We need the subject claim to lookup the user so we can get their key and
51 // validate the token signature.
52 untrustedClaims := jwt.Claims{}
53 if err := parsed.UnsafeClaimsWithoutVerification(&untrustedClaims); err != nil {
54 return nil, err
55 }
56
57 user, err := m.Store.Get(ctx, untrustedClaims.Subject)
58 if err != nil {
59 return nil, err
60 }
61
62 key := user.GetKey(kid) // Will not return invalid keys
63 if key == nil {
64 return nil, fmt.Errorf("No key in user record for key id %s", kid)
65 }
66
67 claims := jwt.Claims{}
68 if err = parsed.Claims(key.PublicKey, &claims); err != nil {
69 return nil, err
70 }
71
72 if err = claims.Validate(jwt.Expected{
73 Audience: jwt.Audience{m.Audience},
74 Time: time.Now(), // +/- 1 minute
75 }); err != nil {
76 return nil, err
77 }
78
79 // If we made it here then the user matches the public key used to sign the
80 // token and the claims are verified so just return the user record we
81 // fetched earlier.
82
83 return user, nil
84}
85
86// CreateForUser creates a new JWT based on the passed user. In some cases it
87// may also need to generate a new SessionKey for the purpose of encrypting
88// those JWTs. Callers should be sure to save the returned SessionKey, if there
89// is one, to the user when setting the token otherwise the token signature
90// will not be able to be validated on subsequent requests.
91func (m *JWTManager) CreateForUser(u *models.User) (string, *models.SessionKey, error) {
92 pk, err := models.GenerateSessionKey(m.TokenExpires)
93 if err != nil {
94 return "", nil, err
95 }
96
97 // The ExtraHeaders bit is kind of an ugly hack but there's no way in the
98 // official API to set the KeyID for single-signer tokens. However, if the
99 // magic "kid" label is set in the extra headers it will be set as the
100 // KeyID in the header when that's deserialized.
101 signer, err := jose.NewSigner(jose.SigningKey{
102 Algorithm: jose.ES256,
103 Key: pk.PrivateKey,
104 }, &jose.SignerOptions{
105 ExtraHeaders: map[jose.HeaderKey]interface{}{
106 keyIdHeader: pk.KeyId,
107 },
108 })
109 if err != nil {
110 return "", nil, err
111 }
112
113 now := time.Now()
114 j, err := jwt.Signed(signer).Claims(jwt.Claims{
115 Issuer: m.Audience,
116 Subject: u.Username,
117 Audience: jwt.Audience{m.Audience},
118 Expiry: jwt.NewNumericDate(now.Add(m.TokenExpires)),
119 IssuedAt: jwt.NewNumericDate(now),
120 }).CompactSerialize()
121
122 return j, pk, err
123}
diff --git a/cloud/aws/aws.go b/cloud/aws/aws.go
new file mode 100644
index 0000000..180b2c4
--- /dev/null
+++ b/cloud/aws/aws.go
@@ -0,0 +1,218 @@
1package aws
2
3import (
4 "encoding/json"
5 "fmt"
6 "io/ioutil"
7 "net/http"
8 "net/url"
9 "strconv"
10
11 "code.crute.us/mcrute/cloud-identity-broker/app/models"
12
13 "code.crute.us/mcrute/golib/vault"
14 "github.com/aws/aws-sdk-go/aws"
15 "github.com/aws/aws-sdk-go/aws/credentials"
16 "github.com/aws/aws-sdk-go/aws/session"
17 "github.com/aws/aws-sdk-go/service/ec2"
18 "github.com/aws/aws-sdk-go/service/sts"
19)
20
21// defaultRegion is the region where AWS currently has may authoritative
22// service like IAM and ancillary auth services.
23var defaultRegion = aws.String("us-east-1")
24
25type Region struct {
26 Name string
27 Enabled bool
28}
29
30type AWSClient interface {
31 AssumeRole(string, *string) (*sts.Credentials, error)
32 GetFederationURL(string, string) (string, error)
33 GetRegionList() ([]*Region, error)
34}
35
36// account models the account configuration stored in Vault for an AWS account
37// with assumable roles that are stored within a kv JSON record.
38type account struct {
39 AccessKeyId string
40 SecretAccessKey string
41 Roles map[string]struct {
42 ARN string
43 ExternalId string
44 }
45}
46
47type client struct {
48 AccessKeyId string
49 SecretAccessKey string
50 ARN string
51 ExternalId string
52 ConsoleSessionDurationSecs int64
53}
54
55var _ AWSClient = (*client)(nil)
56
57// NewAWSClientFromAccount returns a new AWSClient based on an account.
58//
59// An account is actually more accurately called an assumable role. Each
60// account contains a vault material set path which is used to fetch all of the
61// credentials for that AWS account and is filtered to one assumable role ARN
62// which is used as the scope for this AWS client. Thus even if an account has
63// multiple roles there must be one instance of the AWS client per account/role
64// pair.
65func NewAWSClientFromAccount(a *models.Account) (AWSClient, error) {
66 var ac account
67 if err := vault.GetVaultKeyStruct(a.VaultMaterial, &ac); err != nil {
68 return nil, err
69 }
70
71 r, ok := ac.Roles[a.ShortName]
72 if !ok {
73 return nil, fmt.Errorf("No roles for account %s in vault response", a.ShortName)
74 }
75
76 return &client{
77 AccessKeyId: ac.AccessKeyId,
78 SecretAccessKey: ac.SecretAccessKey,
79 ARN: r.ARN,
80 ExternalId: r.ExternalId,
81 ConsoleSessionDurationSecs: a.ConsoleSessionDurationSecs(),
82 }, nil
83}
84
85// AssumeRole uses an IAM user credential with higher privilege to assume a
86// role in an AWS account and region. It returns the STS credentials.
87//
88// Not all credentials work for all regions. For newer regions and opt-in
89// regions AWS has been siloing assumed role credentials to that region so it's
90// important to use the correct regional endpoint to fetch the credentials.
91//
92// Note that this is not simply a passthrough to Vault's AWS backend because
93// the Vault backend works by assuming roles and when assuming roles with an
94// assumed role AWS limits the chained role lifetime to 1 hour which doesn't
95// work depending on how the upstream web application tied to this client
96// works. This method instead uses a long-lived IAM user credential to assume a
97// role, which has a limited lifetime which is typically greater than 1 hour.
98func (a *client) AssumeRole(user string, region *string) (*sts.Credentials, error) {
99 if region != nil && *region == "global" {
100 region = nil
101 }
102
103 c := sts.New(session.New(&aws.Config{
104 Region: region,
105 Credentials: credentials.NewStaticCredentials(
106 a.AccessKeyId,
107 a.SecretAccessKey,
108 "",
109 ),
110 }))
111 result, err := c.AssumeRole(&sts.AssumeRoleInput{
112 ExternalId: aws.String(a.ExternalId),
113 RoleArn: aws.String(a.ARN),
114 RoleSessionName: aws.String(user),
115 DurationSeconds: aws.Int64(a.ConsoleSessionDurationSecs),
116 })
117 if err != nil {
118 return nil, err
119 }
120
121 return result.Credentials, nil
122}
123
124// GetRegionList returns all of the EC2 regions that are enabled for an
125// account.
126//
127// All regions should return the same answer to this query so just send it to
128// the default region.
129func (a *client) GetRegionList() ([]*Region, error) {
130 r, err := a.AssumeRole("region-list", defaultRegion)
131 if err != nil {
132 return nil, err
133 }
134
135 ec2c := ec2.New(session.New(&aws.Config{
136 Region: defaultRegion,
137 Credentials: credentials.NewStaticCredentials(
138 *r.AccessKeyId,
139 *r.SecretAccessKey,
140 *r.SessionToken,
141 ),
142 }))
143
144 ro, err := ec2c.DescribeRegions(&ec2.DescribeRegionsInput{AllRegions: aws.Bool(true)})
145 if err != nil {
146 return nil, err
147 }
148
149 out := []*Region{}
150 for _, r := range ro.Regions {
151 out = append(out, &Region{
152 Name: *r.RegionName,
153 Enabled: (*r.OptInStatus != "not-opted-in"),
154 })
155 }
156
157 return out, nil
158}
159
160// GetFederationURL assumes a role and returns a URL that can be used to login
161// to the AWS console for that role.
162//
163// This URL can be used for any region but the endpoints to return the URL only
164// work in us-east-1.
165func (a *client) GetFederationURL(user string, issuerEndpoint string) (string, error) {
166 r, err := a.AssumeRole(user, defaultRegion)
167 if err != nil {
168 return "", err
169 }
170
171 sp, _ := json.Marshal(map[string]string{
172 "sessionId": *r.AccessKeyId,
173 "sessionKey": *r.SecretAccessKey,
174 "sessionToken": *r.SessionToken,
175 })
176
177 client := &http.Client{}
178 fedResp, err := client.Do(&http.Request{
179 Method: http.MethodGet,
180 URL: &url.URL{
181 Scheme: "https",
182 Host: "signin.aws.amazon.com",
183 Path: "/federation",
184 RawQuery: url.Values{
185 "Action": []string{"getSigninToken"},
186 "Session": []string{string(sp)},
187 "SessionDuration": []string{strconv.FormatInt(a.ConsoleSessionDurationSecs, 10)},
188 }.Encode(),
189 },
190 })
191 if err != nil {
192 return "", err
193 }
194
195 if fedResp.StatusCode != 200 {
196 return "", fmt.Errorf("federation endpoint returned HTTP %d", fedResp.StatusCode)
197 }
198
199 body, err := ioutil.ReadAll(fedResp.Body)
200 if err != nil {
201 return "", err
202 }
203
204 var fedData map[string]string
205 if err = json.Unmarshal(body, &fedData); err != nil {
206 return "", err
207 }
208
209 u, _ := url.Parse("https://signin.aws.amazon.com/federation")
210 u.RawQuery = url.Values{
211 "Action": []string{"login"},
212 "Issuer": []string{issuerEndpoint},
213 "Destination": []string{"https://console.aws.amazon.com/"},
214 "SigninToken": []string{fedData["SigninToken"]},
215 }.Encode()
216
217 return u.String(), nil
218}
diff --git a/cloud/aws/error.go b/cloud/aws/error.go
new file mode 100644
index 0000000..62fda48
--- /dev/null
+++ b/cloud/aws/error.go
@@ -0,0 +1,11 @@
1package aws
2
3import (
4 "strings"
5)
6
7// IsRegionNotExist tries to determine if the error is caused by a region not
8// existing, as would be the case in a user typo.
9func IsRegionNotExist(err error) bool {
10 return strings.Contains(err.Error(), "no such host")
11}
diff --git a/cmd/web/server.go b/cmd/web/server.go
new file mode 100644
index 0000000..e76b6b2
--- /dev/null
+++ b/cmd/web/server.go
@@ -0,0 +1,155 @@
1package web
2
3import (
4 "context"
5 "io/fs"
6 "log"
7 "os"
8 "time"
9
10 "code.crute.us/mcrute/cloud-identity-broker/app"
11 "code.crute.us/mcrute/cloud-identity-broker/app/controllers"
12 "code.crute.us/mcrute/cloud-identity-broker/app/middleware"
13 "code.crute.us/mcrute/cloud-identity-broker/app/models"
14 "code.crute.us/mcrute/cloud-identity-broker/auth"
15 "code.crute.us/mcrute/cloud-identity-broker/auth/github"
16
17 "code.crute.us/mcrute/golib/crypto/tls"
18 "code.crute.us/mcrute/golib/db/mongodb"
19 glecho "code.crute.us/mcrute/golib/echo"
20 glmw "code.crute.us/mcrute/golib/echo/middleware"
21 "code.crute.us/mcrute/golib/service"
22 "github.com/labstack/echo/v4"
23 echomw "github.com/labstack/echo/v4/middleware"
24 "github.com/spf13/cobra"
25 "golang.org/x/time/rate"
26)
27
28func Register(root *cobra.Command, embeddedTemplates fs.FS) {
29 webCmd := &cobra.Command{
30 Use: "web [options]",
31 Short: "Run web server",
32 Run: func(c *cobra.Command, args []string) {
33 webMain(app.NewConfigFromCmd(c), embeddedTemplates)
34 },
35 }
36
37 webCmd.Flags().StringSlice("bind", []string{":8169"}, "Addresses and ports to bind http server")
38 webCmd.Flags().StringSlice("bind-tls", []string{":8170"}, "Addresses and ports to bind https server")
39 webCmd.Flags().String("log-file", "", "Log file for combined host logs")
40 webCmd.Flags().String("tls-cache-dir", "ssl/", "Cache directory for TLS certificates")
41 webCmd.Flags().StringSlice("trusted-ip-ranges", []string{"172.19.0.0/22", "2602:803:4072::/48"}, "Comma separated list of IP ranges for trusted XFF proxies")
42 webCmd.Flags().StringSlice("management-ip-ranges", []string{"127.0.0.1/32", "::1/128", "172.19.0.0/22", "2602:803:4072::/48"}, "IP ranges for management systems")
43 webCmd.Flags().StringSlice("hostname", []string{"dev.aws-access.crute.us"}, "Hostname this server serves (can be specified multiple times)")
44 webCmd.Flags().Duration("rate-limit", 30*time.Second, "Number seconds between requests for credential resources")
45 webCmd.Flags().Duration("auth-cookie-duration", 24*time.Hour, "Expiration duration of the auth cookies")
46 webCmd.Flags().Int("rate-limit-burst", 30, "Number of burst requests allowed to credential endpoints")
47 webCmd.Flags().String("issuer-endpoint", "https://aws-access.crute.us", "Oauth issuer endpoint")
48 webCmd.Flags().String("jwt-audience", "aws-access", "Audience for issued JWTs")
49
50 webCmd.Flags().String("github-oauth-vault-path", "", "Vault material name for GitHub auth credentials")
51 webCmd.MarkFlagRequired("github-oauth-vault-path")
52
53 root.AddCommand(webCmd)
54}
55
56func webMain(cfg app.Config, embeddedTemplates fs.FS) {
57 ctx := context.Background()
58
59 s, err := glecho.NewDefaultEchoWithConfig(glecho.EchoConfig{
60 Debug: cfg.Debug,
61 Hostnames: cfg.Hostnames,
62 BindAddresses: cfg.Bind,
63 BindTLSAddresses: cfg.BindTLS,
64 TLSCacheDir: cfg.TLSCacheDir,
65 TrustedProxyIPRanges: cfg.TrustedIPRanges,
66 ManagementIPRanges: cfg.ManagementIPRanges,
67 DiskTemplates: os.DirFS(cfg.TemplatePath),
68 EmbeddedTemplates: embeddedTemplates,
69 TemplateGlob: &cfg.TemplateGlob,
70 CombinedHostLogFile: cfg.LogFile,
71 ContentSecurityPolicy: &glmw.ContentSecurityPolicyConfig{
72 DefaultSrc: []glmw.CSPDirective{
73 glmw.CSPSelf,
74 glmw.CSPUnsafeInline,
75 },
76 StyleSrc: []glmw.CSPDirective{
77 glmw.CSPSelf,
78 glmw.CSPData,
79 glmw.CSPUnsafeInline,
80 },
81 },
82 })
83 if err != nil {
84 log.Fatalf("Error building echo: %w", err)
85 }
86 if err = s.Init(); err != nil {
87 log.Fatalf("Error initializing echo: %w", err)
88 }
89
90 mongo, err := mongodb.Connect(ctx, cfg.MongoDbUri, cfg.MongodbVaultPath)
91 if err != nil {
92 log.Fatalf("Error connecting to mongodb: %w", err)
93 }
94
95 rateLimit := echomw.RateLimiter(
96 echomw.NewRateLimiterMemoryStoreWithConfig(
97 echomw.RateLimiterMemoryStoreConfig{
98 Rate: rate.Every(cfg.RateLimit),
99 Burst: cfg.RateLimitBurst,
100 ExpiresIn: time.Hour,
101 },
102 ),
103 )
104
105 as := &models.MongoDbAccountStore{Db: mongo}
106 us := &models.MongoDbUserStore{Db: mongo}
107
108 aws := &controllers.AWSAPI{Store: as}
109
110 am := &middleware.AuthenticationMiddleware{
111 Store: us,
112 CookieDuration: cfg.AuthCookieDuration,
113 GitHub: &github.GitHubAuthenticator{
114 ClientId: cfg.GitHubOauthCreds.ClientId,
115 ClientSecret: cfg.GitHubOauthCreds.ClientSecret,
116 },
117 JWTManager: &auth.JWTManager{
118 Store: us,
119 Audience: cfg.JWTAudience,
120 TokenExpires: cfg.AuthCookieDuration,
121 },
122 }
123 am.RegisterUrls(s)
124
125 api := s.Group("/api/account")
126 api.Use(glmw.VaryCookie())
127 api.Use(glmw.CacheNeverMiddleware)
128 api.Use(am.Middleware)
129 {
130 api.GET("", controllers.NewAPIAccountListHandler(as))
131 api.GET(
132 "/:account/credentials",
133 controllers.NewAPIRegionListHandler(aws),
134 )
135 api.GET(
136 "/:account/console",
137 controllers.NewAPIConsoleRedirectHandler(aws, cfg.IssuerEndpoint),
138 rateLimit,
139 )
140 api.GET(
141 "/:account/credentials/:region",
142 controllers.NewAPICredentialsHandler(aws),
143 rateLimit,
144 )
145 }
146 s.GET("/favicon.ico", echo.NotFoundHandler)
147 s.GET("/logout", controllers.LogoutHandler)
148 s.GET("/", controllers.IndexHandler, am.Middleware)
149
150 runner := service.NewAppRunner(ctx, s.Logger)
151 runner.AddJob(s.RunCertificateManager) // Cert manager has to start before server
152 runner.AddJob(tls.OcspErrorLogger(s.Logger, s.OcspErrors()))
153 runner.AddJobs(s.MakeServerJobs())
154 runner.RunForever(!cfg.DisableBackgroundJobs)
155}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..c819322
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,86 @@
1module code.crute.us/mcrute/cloud-identity-broker
2
3go 1.17
4
5require (
6 code.crute.us/mcrute/golib v0.3.0
7 code.crute.us/mcrute/golib/cli v0.1.2
8 code.crute.us/mcrute/golib/db/mongodb v0.2.0
9 code.crute.us/mcrute/golib/echo v0.5.0
10 code.crute.us/mcrute/golib/vault v0.1.2
11 github.com/aws/aws-sdk-go v1.42.4
12 github.com/labstack/echo/v4 v4.6.1
13 github.com/prometheus/client_golang v1.11.0
14 github.com/spf13/cobra v1.2.1
15 go.mongodb.org/mongo-driver v1.7.4
16 golang.org/x/time v0.0.0-20201208040808-7e3f01d25324
17 gopkg.in/square/go-jose.v2 v2.5.1
18)
19
20require (
21 github.com/armon/go-metrics v0.3.9 // indirect
22 github.com/armon/go-radix v1.0.0 // indirect
23 github.com/beorn7/perks v1.0.1 // indirect
24 github.com/cenkalti/backoff/v3 v3.0.0 // indirect
25 github.com/cespare/xxhash/v2 v2.1.1 // indirect
26 github.com/elnormous/contenttype v1.0.0 // indirect
27 github.com/fatih/color v1.7.0 // indirect
28 github.com/go-stack/stack v1.8.0 // indirect
29 github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
30 github.com/golang/protobuf v1.5.2 // indirect
31 github.com/golang/snappy v0.0.4 // indirect
32 github.com/hashicorp/errwrap v1.1.0 // indirect
33 github.com/hashicorp/go-cleanhttp v0.5.1 // indirect
34 github.com/hashicorp/go-hclog v0.16.2 // indirect
35 github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
36 github.com/hashicorp/go-multierror v1.1.1 // indirect
37 github.com/hashicorp/go-plugin v1.4.3 // indirect
38 github.com/hashicorp/go-retryablehttp v0.6.6 // indirect
39 github.com/hashicorp/go-rootcerts v1.0.2 // indirect
40 github.com/hashicorp/go-secure-stdlib/mlock v0.1.1 // indirect
41 github.com/hashicorp/go-secure-stdlib/parseutil v0.1.1 // indirect
42 github.com/hashicorp/go-secure-stdlib/strutil v0.1.1 // indirect
43 github.com/hashicorp/go-sockaddr v1.0.2 // indirect
44 github.com/hashicorp/go-uuid v1.0.2 // indirect
45 github.com/hashicorp/go-version v1.2.0 // indirect
46 github.com/hashicorp/golang-lru v0.5.4 // indirect
47 github.com/hashicorp/hcl v1.0.0 // indirect
48 github.com/hashicorp/vault/api v1.3.0 // indirect
49 github.com/hashicorp/vault/sdk v0.3.0 // indirect
50 github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb // indirect
51 github.com/inconshreveable/mousetrap v1.0.0 // indirect
52 github.com/jmespath/go-jmespath v0.4.0 // indirect
53 github.com/klauspost/compress v1.13.6 // indirect
54 github.com/labstack/gommon v0.3.1 // indirect
55 github.com/mattn/go-colorable v0.1.11 // indirect
56 github.com/mattn/go-isatty v0.0.14 // indirect
57 github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
58 github.com/mitchellh/copystructure v1.0.0 // indirect
59 github.com/mitchellh/go-homedir v1.1.0 // indirect
60 github.com/mitchellh/go-testing-interface v1.0.0 // indirect
61 github.com/mitchellh/mapstructure v1.4.2 // indirect
62 github.com/mitchellh/reflectwalk v1.0.0 // indirect
63 github.com/oklog/run v1.0.0 // indirect
64 github.com/pierrec/lz4 v2.5.2+incompatible // indirect
65 github.com/pkg/errors v0.9.1 // indirect
66 github.com/prometheus/client_model v0.2.0 // indirect
67 github.com/prometheus/common v0.32.1 // indirect
68 github.com/prometheus/procfs v0.6.0 // indirect
69 github.com/ryanuber/go-glob v1.0.0 // indirect
70 github.com/spf13/pflag v1.0.5 // indirect
71 github.com/valyala/bytebufferpool v1.0.0 // indirect
72 github.com/valyala/fasttemplate v1.2.1 // indirect
73 github.com/xdg-go/pbkdf2 v1.0.0 // indirect
74 github.com/xdg-go/scram v1.0.2 // indirect
75 github.com/xdg-go/stringprep v1.0.2 // indirect
76 github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
77 go.uber.org/atomic v1.9.0 // indirect
78 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
79 golang.org/x/net v0.0.0-20210913180222-943fd674d43e // indirect
80 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
81 golang.org/x/sys v0.0.0-20211103235746-7861aae1554b // indirect
82 golang.org/x/text v0.3.7 // indirect
83 google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c // indirect
84 google.golang.org/grpc v1.41.0 // indirect
85 google.golang.org/protobuf v1.26.0 // indirect
86)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..ffbddbe
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,899 @@
1cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
2cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
3cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
4cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
5cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
6cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
7cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
8cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
9cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
10cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
11cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
12cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
13cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
14cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
15cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
16cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
17cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
18cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
19cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
20cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
21cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
22cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
23cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
24cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
25cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
26cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
27cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
28cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
29cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
30cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
31cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
32cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
33cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
34cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
35cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
36cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
37cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
38cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
39code.crute.us/mcrute/golib v0.1.1/go.mod h1:VOnYQYqBYC3NUYPKwbzYSHW/BUBBU5RX7Z+A9nlJZUc=
40code.crute.us/mcrute/golib v0.3.0 h1:7g45xUf/din4VZkKAIK+bPCfXVTlwnZYS4Jv+/lUQ74=
41code.crute.us/mcrute/golib v0.3.0/go.mod h1:VOnYQYqBYC3NUYPKwbzYSHW/BUBBU5RX7Z+A9nlJZUc=
42code.crute.us/mcrute/golib/cli v0.1.2 h1:Yeg+8Jcm5FSYxFvebIGGmDJqmaDCgxl6QPb0GTTMXi0=
43code.crute.us/mcrute/golib/cli v0.1.2/go.mod h1:qhim2CV3zsMflpCbTMJs7dKnzzVIdBkSvm4jHDyXgik=
44code.crute.us/mcrute/golib/db/mongodb v0.2.0 h1:tumWZET3BgkutMWkeLLhIYy0c7dgEHa6mABF6oE1Y1o=
45code.crute.us/mcrute/golib/db/mongodb v0.2.0/go.mod h1:JUX7PU8mUu68Y4sOERbZKON+x5A7cIxgxifxpXw//Bs=
46code.crute.us/mcrute/golib/echo v0.5.0 h1:M8D69fCopxLee4rTYmPswNe2OVI7x7OmQOsr5P9nyUU=
47code.crute.us/mcrute/golib/echo v0.5.0/go.mod h1:rNrjiYlJDwkabv0alUpSIsS/tR6HeBoP90UHaPUiL+Q=
48code.crute.us/mcrute/golib/vault v0.1.1/go.mod h1:kr+P3q7WJ/+dKieQJ3ZMccWeWV0M3KyHu6Ofghn0H7U=
49code.crute.us/mcrute/golib/vault v0.1.2 h1:L80WffgReTtL8FUV83GRAYjPzhAQo4G+h1y1n3CkZEU=
50code.crute.us/mcrute/golib/vault v0.1.2/go.mod h1:kr+P3q7WJ/+dKieQJ3ZMccWeWV0M3KyHu6Ofghn0H7U=
51dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
52github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
53github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
54github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
55github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
56github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
57github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
58github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
59github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
60github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
61github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
62github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
63github.com/armon/go-metrics v0.3.9 h1:O2sNqxBdvq8Eq5xmzljcYzAORli6RWCvEym4cJf9m18=
64github.com/armon/go-metrics v0.3.9/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc=
65github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
66github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
67github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
68github.com/aws/aws-sdk-go v1.42.4 h1:L3gadqlmmdWCDE7aD52l3A5TKVG9jPBHZG1/65x9GVw=
69github.com/aws/aws-sdk-go v1.42.4/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
70github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
71github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
72github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
73github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
74github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
75github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
76github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3eG1c=
77github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs=
78github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
79github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
80github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
81github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
82github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
83github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
84github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
85github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
86github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
87github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
88github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
89github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
90github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
91github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
92github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
93github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
94github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
95github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
96github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
97github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
98github.com/elnormous/contenttype v1.0.0 h1:cTLou7K7uQMsPEmRiTJosAznsPcYuoBmXMrFAf86t2A=
99github.com/elnormous/contenttype v1.0.0/go.mod h1:ngVcyGGU8pnn4QJ5sL4StrNgc/wmXZXy5IQSBuHOFPg=
100github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
101github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
102github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
103github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
104github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
105github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
106github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
107github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
108github.com/evanphx/json-patch/v5 v5.5.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4=
109github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
110github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
111github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
112github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
113github.com/frankban/quicktest v1.10.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y=
114github.com/frankban/quicktest v1.13.0 h1:yNZif1OkDfNoDfb9zZa9aXIpejNR4F23Wely0c+Qdqk=
115github.com/frankban/quicktest v1.13.0/go.mod h1:qLE0fzW0VuyUAJgPU19zByoIr0HtCHN/r/VLSOOIySU=
116github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
117github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
118github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
119github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
120github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
121github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
122github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
123github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
124github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
125github.com/go-ldap/ldap/v3 v3.1.10/go.mod h1:5Zun81jBTabRaI8lzN7E1JjyEl1g6zI6u9pd8luAK4Q=
126github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
127github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
128github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
129github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
130github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
131github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw=
132github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
133github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0=
134github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY=
135github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg=
136github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
137github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
138github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs=
139github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI=
140github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI=
141github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk=
142github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28=
143github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo=
144github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk=
145github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw=
146github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360=
147github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg=
148github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE=
149github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8=
150github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc=
151github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc=
152github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4=
153github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4=
154github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ=
155github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0=
156github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw=
157github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
158github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
159github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
160github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
161github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
162github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
163github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
164github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
165github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
166github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
167github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
168github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
169github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
170github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
171github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
172github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
173github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
174github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
175github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
176github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
177github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
178github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
179github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
180github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
181github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
182github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
183github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
184github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
185github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
186github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
187github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
188github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
189github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
190github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
191github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
192github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
193github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
194github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
195github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
196github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
197github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
198github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
199github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
200github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
201github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
202github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
203github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
204github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
205github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
206github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
207github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
208github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
209github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
210github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
211github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
212github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
213github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
214github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
215github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
216github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
217github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
218github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
219github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
220github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
221github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
222github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
223github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
224github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
225github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
226github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
227github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
228github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
229github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
230github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
231github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
232github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
233github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
234github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
235github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
236github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM=
237github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
238github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
239github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
240github.com/hashicorp/go-hclog v0.16.2 h1:K4ev2ib4LdQETX5cSZBG0DVLk1jwGqSPXBjdah3veNs=
241github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
242github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
243github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc=
244github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
245github.com/hashicorp/go-kms-wrapping/entropy v0.1.0/go.mod h1:d1g9WGtAunDNpek8jUIEJnBlbgKS1N2Q61QkHiZyR1g=
246github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
247github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
248github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
249github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
250github.com/hashicorp/go-plugin v1.4.3 h1:DXmvivbWD5qdiBts9TpBC7BYL1Aia5sxbRgQB+v6UZM=
251github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ=
252github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
253github.com/hashicorp/go-retryablehttp v0.6.6 h1:HJunrbHTDDbBb/ay4kxa1n+dLmttUlnP3V9oNE4hmsM=
254github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
255github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
256github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
257github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
258github.com/hashicorp/go-secure-stdlib/base62 v0.1.1/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw=
259github.com/hashicorp/go-secure-stdlib/mlock v0.1.1 h1:cCRo8gK7oq6A2L6LICkUZ+/a5rLiRXFMf1Qd4xSwxTc=
260github.com/hashicorp/go-secure-stdlib/mlock v0.1.1/go.mod h1:zq93CJChV6L9QTfGKtfBxKqD7BqqXx5O04A/ns2p5+I=
261github.com/hashicorp/go-secure-stdlib/parseutil v0.1.1 h1:78ki3QBevHwYrVxnyVeaEz+7WtifHhauYF23es/0KlI=
262github.com/hashicorp/go-secure-stdlib/parseutil v0.1.1/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8=
263github.com/hashicorp/go-secure-stdlib/password v0.1.1/go.mod h1:9hH302QllNwu1o2TGYtSk8I8kTAN0ca1EHpwhm5Mmzo=
264github.com/hashicorp/go-secure-stdlib/strutil v0.1.1 h1:nd0HIW15E6FG1MsnArYaHfuw9C2zgzM8LxkG5Ty/788=
265github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U=
266github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.1/go.mod h1:l8slYwnJA26yBz+ErHpp2IRCLr0vuOMGBORIz4rRiAs=
267github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
268github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc=
269github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
270github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
271github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
272github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
273github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
274github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
275github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E=
276github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
277github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
278github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
279github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
280github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
281github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
282github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
283github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
284github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
285github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
286github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
287github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
288github.com/hashicorp/vault/api v1.3.0 h1:uDy39PLSvy6gtKyjOCRPizy2QdFiIYSWBR2pxCEzYL8=
289github.com/hashicorp/vault/api v1.3.0/go.mod h1:EabNQLI0VWbWoGlA+oBLC8PXmR9D60aUVgQGvangFWQ=
290github.com/hashicorp/vault/sdk v0.3.0 h1:kR3dpxNkhh/wr6ycaJYqp6AFT/i2xaftbfnwZduTKEY=
291github.com/hashicorp/vault/sdk v0.3.0/go.mod h1:aZ3fNuL5VNydQk8GcLJ2TV8YCRVvyaakYkhZRoVuhj0=
292github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M=
293github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
294github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
295github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
296github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
297github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
298github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
299github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE=
300github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74=
301github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
302github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
303github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
304github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
305github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
306github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
307github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
308github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
309github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
310github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
311github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
312github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
313github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
314github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
315github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
316github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4=
317github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
318github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
319github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
320github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
321github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
322github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
323github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
324github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
325github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
326github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
327github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
328github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
329github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
330github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
331github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
332github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
333github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
334github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
335github.com/labstack/echo/v4 v4.6.1 h1:OMVsrnNFzYlGSdaiYGHbgWQnr+JM7NG+B9suCPie14M=
336github.com/labstack/echo/v4 v4.6.1/go.mod h1:RnjgMWNDB9g/HucVWhQYNQP9PvbYf6adqftqryo7s9k=
337github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
338github.com/labstack/gommon v0.3.1 h1:OomWaJXm7xR6L1HmEtGyQf26TEn7V6X88mktX9kee9o=
339github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
340github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
341github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
342github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
343github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
344github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
345github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
346github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
347github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
348github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs=
349github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
350github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
351github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
352github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
353github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
354github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
355github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
356github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
357github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
358github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
359github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
360github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
361github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
362github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
363github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
364github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
365github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
366github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
367github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0=
368github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
369github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
370github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
371github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
372github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
373github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
374github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
375github.com/mitchellh/mapstructure v1.4.2 h1:6h7AQ0yhTcIsmFmnAwQls75jp2Gzs4iB8W7pjMO+rqo=
376github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
377github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
378github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
379github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
380github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
381github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
382github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
383github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
384github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
385github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
386github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
387github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
388github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
389github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
390github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
391github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
392github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
393github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI=
394github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
395github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
396github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
397github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
398github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
399github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
400github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
401github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
402github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
403github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
404github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
405github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
406github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
407github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ=
408github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
409github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
410github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
411github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
412github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
413github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
414github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
415github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
416github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
417github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
418github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4=
419github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
420github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
421github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
422github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
423github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
424github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
425github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
426github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
427github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
428github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
429github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
430github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
431github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
432github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
433github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
434github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
435github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
436github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
437github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
438github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
439github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
440github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
441github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
442github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
443github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
444github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
445github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
446github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
447github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw=
448github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
449github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
450github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
451github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
452github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
453github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
454github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
455github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
456github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
457github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
458github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
459github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
460github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
461github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
462github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
463github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
464github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
465github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
466github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
467github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
468github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
469github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
470github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
471github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4=
472github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
473github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
474github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
475github.com/xdg-go/scram v1.0.2 h1:akYIkZ28e6A96dkWNJQu3nmCzH3YfwMPQExUYDaRv7w=
476github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
477github.com/xdg-go/stringprep v1.0.2 h1:6iq84/ryjjeRmMJwxutI51F2GIPlP5BfTvXHeYjyhBc=
478github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
479github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA=
480github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
481github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
482github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
483github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
484github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
485github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
486go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
487go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
488go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
489go.mongodb.org/mongo-driver v1.7.4 h1:sllcioag8Mec0LYkftYWq+cKNPIR4Kqq3iv9ZXY0g/E=
490go.mongodb.org/mongo-driver v1.7.4/go.mod h1:NqaYOwnXWr5Pm7AOpO5QFxKJ503nbMse/R79oO62zWg=
491go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
492go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
493go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
494go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
495go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
496go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
497go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
498go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
499go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
500go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
501go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
502go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
503go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
504golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
505golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
506golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
507golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
508golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
509golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
510golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
511golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
512golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
513golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
514golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
515golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
516golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
517golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
518golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
519golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
520golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
521golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
522golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
523golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
524golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
525golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
526golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
527golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
528golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
529golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
530golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
531golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
532golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
533golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
534golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
535golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
536golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
537golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
538golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
539golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
540golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
541golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
542golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
543golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
544golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
545golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
546golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
547golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
548golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
549golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
550golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
551golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
552golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
553golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
554golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
555golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
556golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
557golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
558golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
559golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
560golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
561golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
562golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
563golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
564golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
565golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
566golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
567golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
568golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
569golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
570golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
571golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
572golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
573golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
574golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
575golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
576golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
577golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
578golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
579golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
580golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
581golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
582golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
583golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
584golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
585golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
586golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
587golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
588golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
589golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
590golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
591golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
592golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
593golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
594golang.org/x/net v0.0.0-20210913180222-943fd674d43e h1:+b/22bPvDYt4NPDcy4xAGCmON713ONAWFeY3Z7I3tR8=
595golang.org/x/net v0.0.0-20210913180222-943fd674d43e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
596golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
597golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
598golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
599golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
600golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
601golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
602golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
603golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
604golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
605golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
606golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
607golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
608golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
609golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
610golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
611golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
612golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
613golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
614golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
615golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
616golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
617golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
618golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
619golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
620golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
621golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
622golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
623golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
624golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
625golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
626golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
627golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
628golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
629golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
630golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
631golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
632golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
633golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
634golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
635golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
636golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
637golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
638golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
639golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
640golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
641golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
642golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
643golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
644golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
645golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
646golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
647golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
648golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
649golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
650golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
651golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
652golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
653golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
654golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
655golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
656golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
657golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
658golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
659golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
660golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
661golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
662golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
663golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
664golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
665golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
666golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
667golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
668golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
669golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
670golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
671golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
672golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
673golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
674golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
675golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
676golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
677golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
678golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
679golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
680golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
681golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
682golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
683golang.org/x/sys v0.0.0-20211103235746-7861aae1554b h1:1VkfZQv42XQlA/jchYumAnv1UPo6RgF9rJFkTgZIxO4=
684golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
685golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
686golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
687golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
688golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
689golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
690golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
691golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
692golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
693golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
694golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
695golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
696golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
697golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
698golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
699golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
700golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 h1:Hir2P/De0WpUhtrKGGjvSb2YxUgyZ7EFOSLIcSSpiwE=
701golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
702golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
703golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
704golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
705golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
706golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
707golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
708golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
709golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
710golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
711golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
712golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
713golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
714golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
715golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
716golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
717golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
718golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
719golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
720golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
721golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
722golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
723golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
724golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
725golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
726golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
727golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
728golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
729golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
730golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
731golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
732golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
733golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
734golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
735golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
736golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
737golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
738golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
739golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
740golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
741golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
742golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
743golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
744golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
745golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
746golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
747golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
748golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
749golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
750golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
751golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
752golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
753golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
754golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
755golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
756golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
757golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
758golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
759golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
760golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
761golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
762google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
763google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
764google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
765google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
766google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
767google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
768google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
769google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
770google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
771google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
772google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
773google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
774google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
775google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
776google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
777google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
778google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
779google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
780google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
781google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
782google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
783google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8=
784google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
785google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
786google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
787google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
788google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
789google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
790google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
791google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
792google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
793google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
794google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
795google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
796google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
797google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
798google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
799google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
800google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
801google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
802google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
803google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
804google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
805google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
806google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
807google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
808google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
809google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
810google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
811google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
812google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
813google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
814google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
815google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
816google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
817google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
818google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
819google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
820google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
821google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
822google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
823google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
824google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
825google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
826google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
827google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
828google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
829google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
830google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
831google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
832google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c h1:wtujag7C+4D6KMoulW9YauvK2lgdvCMS260jsqqBXr0=
833google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
834google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
835google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
836google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
837google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
838google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
839google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
840google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
841google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
842google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
843google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
844google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
845google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
846google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
847google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
848google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
849google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
850google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
851google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
852google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
853google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
854google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
855google.golang.org/grpc v1.41.0 h1:f+PlOh7QV4iIJkPrx5NQ7qaNGFQ3OTse67yaDHfju4E=
856google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k=
857google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
858google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
859google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
860google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
861google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
862google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
863google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
864google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
865google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
866google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
867google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
868google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
869google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
870gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
871gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
872gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
873gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
874gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
875gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
876gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w=
877gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
878gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
879gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
880gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
881gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
882gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
883gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
884gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
885gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
886gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
887gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
888gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
889gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
890honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
891honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
892honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
893honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
894honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
895honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
896honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
897rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
898rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
899rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..f262847
--- /dev/null
+++ b/main.go
@@ -0,0 +1,45 @@
1package main
2
3import (
4 "embed"
5 "io/fs"
6 "log"
7
8 "code.crute.us/mcrute/cloud-identity-broker/cmd/web"
9
10 "github.com/spf13/cobra"
11
12 // Import backup data. By default zoneinfo is installed in the docker image
13 // if something breaks this will still result in us having correct TZ info.
14 _ "time/tzdata"
15)
16
17//go:embed templates
18var embeddedTemplates embed.FS
19
20func main() {
21 rootCmd := &cobra.Command{
22 Use: "cloud-identity-broker",
23 Short: "Identity broker for cloud account access",
24 }
25
26 rootCmd.PersistentFlags().Bool("debug", false, "Enable debug mode")
27 rootCmd.PersistentFlags().String("template-glob", "*.tpl", "Glob to match templates")
28 rootCmd.PersistentFlags().String("template-path", "templates/", "Path where site files are located")
29 rootCmd.PersistentFlags().String("mongodb-uri", "", "URI for connection to mongodb")
30 rootCmd.PersistentFlags().String("mongodb-vault-path", "", "Database path for mongodb vault credentials")
31 rootCmd.PersistentFlags().Bool("disable-bg-jobs", false, "Disable background jobs and only serve web pages")
32
33 // This is here because the trimmed prefix must match the embed prefix
34 // above and there's no need to bury it and de-couple that relationship.
35 templates, err := fs.Sub(embeddedTemplates, "templates")
36 if err != nil {
37 log.Fatalf("Error building sub-fs of embeded fs")
38 }
39
40 web.Register(rootCmd, templates)
41
42 if err := rootCmd.Execute(); err != nil {
43 log.Fatalf("Error running root command: %s", err)
44 }
45}
diff --git a/templates/404.tpl b/templates/404.tpl
new file mode 100644
index 0000000..e87ed60
--- /dev/null
+++ b/templates/404.tpl
@@ -0,0 +1,10 @@
1<!DOCTYPE html>
2<html lang="en">
3 <head>
4 <title>Not Found</title>
5 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
6 </head>
7 <body>
8 <h1>Requested Resource Not Found</h1>
9 </body>
10</html>
diff --git a/templates/40x.tpl b/templates/40x.tpl
new file mode 100644
index 0000000..c3f55c0
--- /dev/null
+++ b/templates/40x.tpl
@@ -0,0 +1,10 @@
1<!DOCTYPE html>
2<html lang="en">
3 <head>
4 <title>Request Error</title>
5 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
6 </head>
7 <body>
8 <h1>Request Error</h1>
9 </body>
10</html>
diff --git a/templates/50x.tpl b/templates/50x.tpl
new file mode 100644
index 0000000..b8e1702
--- /dev/null
+++ b/templates/50x.tpl
@@ -0,0 +1,10 @@
1<!DOCTYPE html>
2<html lang="en">
3 <head>
4 <title>Server Error</title>
5 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
6 </head>
7 <body>
8 <h1>Server Error</h1>
9 </body>
10</html>
diff --git a/templates/footer.tpl b/templates/footer.tpl
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/templates/footer.tpl
diff --git a/templates/header.tpl b/templates/header.tpl
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/templates/header.tpl
diff --git a/templates/index.tpl b/templates/index.tpl
new file mode 100644
index 0000000..cbc7a72
--- /dev/null
+++ b/templates/index.tpl
@@ -0,0 +1,204 @@
1<!DOCTYPE html>
2<html lang="en">
3 <head>
4 <title>Select Account</title>
5 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
6 <style type="text/css">
7 body {
8 margin: 0 10%;
9 }
10 table {
11 width: 100%;
12 border: 1px solid black;
13 border-collapse: collapse;
14 }
15 td, th {
16 border: 1px solid black;
17 padding: 0.5em;
18 }
19 th {
20 background-color: #CCCCCC;
21 }
22 td.alt-creds {
23 text-align: center;
24 }
25 textarea {
26 height: 10em;
27 width: 100%;
28 }
29 a, a:visited {
30 color: blue;
31 }
32 h1 {
33 text-align: center;
34 }
35 tt {
36 background-color: #CCC;
37 border: 1px solid #999;
38 padding: 0.25em;
39 }
40 #api-key {
41 margin: auto;
42 }
43 #api-key textarea {
44 height: 100%;
45 }
46 #api-error {
47 border: 0.25em solid red;
48 color: white;
49 font-size: 2em;
50 background-color: #ff8080;
51 padding: 1em;
52 text-align: center;
53 display: none;
54 }
55 </style>
56 <script id="shell_template" type="text/template">
57 export AWS_CREDS_EXPIRATION="[[ .expiration ]]"
58 export AWS_ACCESS_KEY_ID="[[ .access_key ]]"
59 export AWS_SECRET_ACCESS_KEY="[[ .secret_key ]]"
60 export AWS_SESSION_TOKEN="[[ .session_token ]]"
61 </script>
62 <script id="powershell_template" type="text/template">
63 Set-Item -path env:AWS_CREDS_EXPIRATION -value '[[ .expiration ]]'
64 Set-Item -path env:AWS_ACCESS_KEY_ID -value '[[ .access_key ]]'
65 Set-Item -path env:AWS_SECRET_ACCESS_KEY -value '[[ .secret_key ]]'
66 Set-Item -path env:AWS_SESSION_TOKEN -value '[[ .session_token ]]'
67 </script>
68 <script id="aws_config_template" type="text/template">
69 [profile [[ .ShortName ]]]
70 aws_access_key_id=[[ .access_key ]]
71 aws_secret_access_key=[[ .secret_key ]]
72 aws_session_token=[[ .session_token ]]
73 expiration=[[ .expiration ]]
74 </script>
75 <script id="credential_row_template" type="text/template">
76 <body>
77 <tr id="credentials-for-[[ .Account ]]">
78 <td colspan="2">
79 <textarea>[[ .Content ]]</textarea>
80 <button>Close</button>
81 </td>
82 </tr>
83 </body>
84 </script>
85 <script id="account_row_template" type="text/template">
86 <tr id="account-row-[[ .short_name ]]" data-account-name="[[ .short_name ]]" data-global-credential-endpoint="[[ .global_credential_url ]]">
87 <td>[[ .name ]]</td>
88 <td class="alt-creds">
89 <a href="[[ .console_redirect_url ]]">Console</a> |
90 <a data-template="aws_config_template" href="#/cli/[[ .short_name ]]">AWS CLI</a> |
91 <a data-template="shell_template" href="#/sh/[[ .short_name ]]">Bash</a> |
92 <a data-template="powershell_template" href="#/ps/[[ .short_name ]]">Powershell</a>
93 </td>
94 </tr>
95 </script>
96 <script type="text/javascript">
97 function fillTemplate(templateId, values) {
98 var tpl = document.getElementById(templateId).text;
99
100 Object.keys(values).forEach(function(key) {
101 tpl = tpl.replace(new RegExp("\\[\\[ \\." + key + " \\]\\]", "g"), values[key]);
102 });
103
104 var out = [];
105 tpl.split("\n").forEach(function(line) {
106 line = line.replace(/^\s+/, "").replace(/\s+$/, "");
107 if (line !== "") {
108 out.push(line);
109 }
110 });
111
112 return out.join("\n");
113 }
114
115 function getJSON(response) {
116 if (!response.ok) {
117 document.getElementById("api-error").style.display = "block";
118 throw new Error("Error loading api");
119 }
120
121 return response.json();
122 }
123
124 function accountTableLinkClick(event) {
125 event.preventDefault();
126
127 var thisRow = event.target.parentElement.parentElement;
128 var template = event.target.getAttribute("data-template");
129 var account = thisRow.getAttribute("data-account-name");
130 var credentialEndpoint = thisRow.getAttribute("data-global-credential-endpoint");
131 var oldText = event.target.text;
132
133 var existingTr = document.getElementById("credentials-for-" + account);
134 if (existingTr) {
135 existingTr.remove();
136 }
137
138 event.target.text = "Loading...";
139
140 fetch(credentialEndpoint).then(getJSON).then(function(vals) {
141 vals["ShortName"] = account;
142
143 var newTr = fillTemplate("credential_row_template", {
144 "Account": account,
145 "Content": fillTemplate(template, vals)
146 });
147
148 event.target.text = oldText;
149 thisRow.insertAdjacentHTML("afterend", newTr);
150 thisRow.nextElementSibling.getElementsByTagName("button")[0].addEventListener("click", function(event) {
151 event.target.parentNode.parentNode.remove();
152 });
153 });
154
155 return false;
156 }
157
158 function populateAccountTable() {
159 fetch("/api/account").then(getJSON).then(function(response) {
160 response.forEach(populateAccountRow);
161 });
162 }
163
164 function populateAccountRow(row) {
165 var out = fillTemplate("account_row_template", row);
166 document.querySelector("#account-table tr").insertAdjacentHTML("afterend", out);
167
168 document.querySelectorAll("#account-row-" + row["short_name"] + " a[data-template]").forEach(function(element) {
169 element.addEventListener("click", accountTableLinkClick);
170 });
171 }
172
173 function getCookie(name) {
174 return document.cookie.match(new RegExp(name + "=\"?([^;\"]*)\"?;?"))[1];
175 }
176
177 function populateAPIKey() {
178 document.querySelector("#api-key textarea").innerText = getCookie("session");
179 document.querySelector("#session-expires").innerText = getCookie("session-expires");
180 }
181
182 window.addEventListener('load', populateAPIKey);
183 window.addEventListener('load', populateAccountTable);
184 </script>
185 </head>
186 <body>
187 <p id="api-error">Error communicating with the API.</p>
188
189 <h1>AWS Accounts</h1>
190 <table id="account-table">
191 <tr>
192 <th>Console Login</th>
193 <th>Get Credentials</th>
194 </tr>
195 </table>
196
197 <h1>API Key</h1>
198 <div id="api-key">
199 <p>To access <a href="/api/account">the API</a> set the value of <tt>Authorization</tt> header to:</p>
200 <textarea>Bearer {token}</textarea>
201 <p><b>Token Expires:</b> <span id="session-expires"></span></p>
202 </div>
203 </body>
204</html>