diff options
author | Mike Crute <mike@crute.us> | 2021-11-16 14:46:24 -0800 |
---|---|---|
committer | Mike Crute <mike@crute.us> | 2021-11-17 07:56:10 -0800 |
commit | cc58a3da7d647de8520e33dc4356672d2ed1a366 (patch) | |
tree | 1b232a0d51446eb6370cfb13932190d31ce053df | |
parent | a42d794a286154a3106551e6e483861af2a9ef16 (diff) | |
download | cloud-identity-broker-cc58a3da7d647de8520e33dc4356672d2ed1a366.tar.bz2 cloud-identity-broker-cc58a3da7d647de8520e33dc4356672d2ed1a366.tar.xz cloud-identity-broker-cc58a3da7d647de8520e33dc4356672d2ed1a366.zip |
Import of source code
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | Dockerfile | 5 | ||||
-rw-r--r-- | Makefile | 40 | ||||
-rw-r--r-- | app/config.go | 70 | ||||
-rw-r--r-- | app/controllers/api.go | 6 | ||||
-rw-r--r-- | app/controllers/api_account_list.go | 109 | ||||
-rw-r--r-- | app/controllers/api_console_redirect.go | 63 | ||||
-rw-r--r-- | app/controllers/api_credentials.go | 76 | ||||
-rw-r--r-- | app/controllers/api_region_list.go | 61 | ||||
-rw-r--r-- | app/controllers/aws.go | 52 | ||||
-rw-r--r-- | app/controllers/basic.go | 17 | ||||
-rw-r--r-- | app/middleware/auth.go | 212 | ||||
-rw-r--r-- | app/models/account.go | 115 | ||||
-rw-r--r-- | app/models/session_key.go | 202 | ||||
-rw-r--r-- | app/models/user.go | 99 | ||||
-rw-r--r-- | auth/github/github.go | 127 | ||||
-rw-r--r-- | auth/jwt.go | 123 | ||||
-rw-r--r-- | cloud/aws/aws.go | 218 | ||||
-rw-r--r-- | cloud/aws/error.go | 11 | ||||
-rw-r--r-- | cmd/web/server.go | 155 | ||||
-rw-r--r-- | go.mod | 86 | ||||
-rw-r--r-- | go.sum | 899 | ||||
-rw-r--r-- | main.go | 45 | ||||
-rw-r--r-- | templates/404.tpl | 10 | ||||
-rw-r--r-- | templates/40x.tpl | 10 | ||||
-rw-r--r-- | templates/50x.tpl | 10 | ||||
-rw-r--r-- | templates/footer.tpl | 0 | ||||
-rw-r--r-- | templates/header.tpl | 0 | ||||
-rw-r--r-- | templates/index.tpl | 204 |
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 @@ | |||
1 | FROM alpine:latest | ||
2 | |||
3 | RUN apk add --no-cache ca-certificates | ||
4 | COPY aws-access-prod / | ||
5 | CMD ["/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 @@ | |||
1 | IMAGE=docker.crute.me/aws-access | ||
2 | BINARY=cloud-identity-broker | ||
3 | |||
4 | $(BINARY): main.go $(shell find . -name '*.go') | ||
5 | go build -o $@ $< | ||
6 | |||
7 | .PHONY: vet | ||
8 | vet: main.go | ||
9 | go vet $< | ||
10 | |||
11 | .PHONY: docker | ||
12 | docker: | ||
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 | ||
18 | publish: | ||
19 | docker push $(IMAGE):stage | ||
20 | |||
21 | .PHONY: publish-prod | ||
22 | publish-prod: | ||
23 | docker tag $(IMAGE):stage $(IMAGE):latest | ||
24 | docker push $(IMAGE):latest | ||
25 | |||
26 | .PHONY: clean | ||
27 | clean: | ||
28 | rm -rf docker || true | ||
29 | rm $(BINARY) || true | ||
30 | |||
31 | .PHONY: run-web | ||
32 | run-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 @@ | |||
1 | package app | ||
2 | |||
3 | import ( | ||
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 | |||
12 | type GitHubOauthCreds struct { | ||
13 | ClientId string `mapstructure:"client-id"` | ||
14 | ClientSecret string `mapstructure:"client-secret"` | ||
15 | } | ||
16 | |||
17 | type 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 | |||
39 | func 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 @@ | |||
1 | package controllers | ||
2 | |||
3 | const ( | ||
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 @@ | |||
1 | package controllers | ||
2 | |||
3 | import ( | ||
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 | |||
15 | type 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 | |||
26 | func 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 | |||
38 | type APIAccountListHandler struct { | ||
39 | store models.AccountStore | ||
40 | } | ||
41 | |||
42 | func 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. | ||
60 | func (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 | ||
76 | func (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. | ||
94 | func (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 @@ | |||
1 | package controllers | ||
2 | |||
3 | import ( | ||
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 | |||
12 | var 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 | |||
18 | type jsonConsoleUrl struct { | ||
19 | ConsoleURL string `json:"console_url"` | ||
20 | } | ||
21 | |||
22 | type APIConsoleRedirectHandler struct { | ||
23 | FederationIssuerEndpoint string | ||
24 | *AWSAPI | ||
25 | } | ||
26 | |||
27 | func 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 | |||
38 | func (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 @@ | |||
1 | package controllers | ||
2 | |||
3 | import ( | ||
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 | |||
15 | var 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 | |||
21 | type 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 | |||
28 | type APICredentialsHandler struct { | ||
29 | *AWSAPI | ||
30 | } | ||
31 | |||
32 | func 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 | |||
43 | func (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, ®ion) | ||
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 @@ | |||
1 | package controllers | ||
2 | |||
3 | import ( | ||
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 | |||
11 | type 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 | |||
18 | type APIRegionListHandler struct { | ||
19 | *AWSAPI | ||
20 | } | ||
21 | |||
22 | func 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 | |||
33 | func (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 @@ | |||
1 | package controllers | ||
2 | |||
3 | import ( | ||
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 | |||
13 | type 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. | ||
22 | type 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. | ||
30 | func (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 @@ | |||
1 | package controllers | ||
2 | |||
3 | import ( | ||
4 | "net/http" | ||
5 | |||
6 | glecho "code.crute.us/mcrute/golib/echo" | ||
7 | "github.com/labstack/echo/v4" | ||
8 | ) | ||
9 | |||
10 | func IndexHandler(c echo.Context) error { | ||
11 | return c.Render(http.StatusOK, "index.tpl", nil) | ||
12 | } | ||
13 | |||
14 | func 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 @@ | |||
1 | package middleware | ||
2 | |||
3 | import ( | ||
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. | ||
20 | type canRegisterUrls interface { | ||
21 | GET(string, echo.HandlerFunc, ...echo.MiddlewareFunc) *echo.Route | ||
22 | } | ||
23 | |||
24 | const ( | ||
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. | ||
38 | func 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 | |||
50 | type AuthenticationMiddleware struct { | ||
51 | Store models.UserStore | ||
52 | JWTManager *auth.JWTManager | ||
53 | GitHub *github.GitHubAuthenticator | ||
54 | CookieDuration time.Duration | ||
55 | } | ||
56 | |||
57 | func (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. | ||
77 | func (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. | ||
101 | func (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. | ||
149 | func (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 @@ | |||
1 | package models | ||
2 | |||
3 | import ( | ||
4 | "context" | ||
5 | "fmt" | ||
6 | "time" | ||
7 | |||
8 | "code.crute.us/mcrute/golib/db/mongodb" | ||
9 | ) | ||
10 | |||
11 | const accountCol = "accounts" | ||
12 | |||
13 | type 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 | |||
22 | type 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 | |||
33 | func (a *Account) ConsoleSessionDurationSecs() int64 { | ||
34 | return int64(a.ConsoleSessionDuration.Seconds()) | ||
35 | } | ||
36 | |||
37 | func (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 | |||
50 | type MongoDbAccountStore struct { | ||
51 | Db *mongodb.Mongo | ||
52 | } | ||
53 | |||
54 | // List returns all accounts in the system. | ||
55 | func (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. | ||
69 | func (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 | |||
78 | func (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. | ||
88 | func (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 | |||
101 | func (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 | |||
108 | func (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 | |||
115 | var _ 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 @@ | |||
1 | package models | ||
2 | |||
3 | import ( | ||
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. | ||
38 | type 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 | |||
48 | func 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: ¬After, | ||
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. | ||
76 | func (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. | ||
95 | func (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 | |||
111 | func (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 | |||
146 | func (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 @@ | |||
1 | package models | ||
2 | |||
3 | import ( | ||
4 | "context" | ||
5 | |||
6 | "code.crute.us/mcrute/golib/db/mongodb" | ||
7 | ) | ||
8 | |||
9 | const userCol = "users" | ||
10 | |||
11 | type 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 | |||
18 | type AuthToken struct { | ||
19 | Kind string | ||
20 | Token string | ||
21 | RefreshToken string | ||
22 | } | ||
23 | |||
24 | type 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 | ||
33 | func (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. | ||
42 | func (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 | |||
51 | func (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 | |||
58 | func (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 | |||
65 | type MongoDbUserStore struct { | ||
66 | Db *mongodb.Mongo | ||
67 | } | ||
68 | |||
69 | func (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 | |||
77 | func (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 | |||
85 | func (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 | |||
92 | func (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 | |||
99 | var _ 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 @@ | |||
1 | package github | ||
2 | |||
3 | import ( | ||
4 | "crypto/rand" | ||
5 | "encoding/base64" | ||
6 | "encoding/json" | ||
7 | "fmt" | ||
8 | "io/ioutil" | ||
9 | "net/http" | ||
10 | "net/url" | ||
11 | "strings" | ||
12 | ) | ||
13 | |||
14 | type apiLoginResponse struct { | ||
15 | Username string `json:"login"` | ||
16 | } | ||
17 | |||
18 | type GitHubToken struct { | ||
19 | AccessToken string `json:"access_token"` | ||
20 | RefreshToken string `json:"refresh_token"` | ||
21 | } | ||
22 | |||
23 | type 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. | ||
31 | func (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. | ||
57 | func (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. | ||
95 | func (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 @@ | |||
1 | package auth | ||
2 | |||
3 | import ( | ||
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 | |||
14 | const keyIdHeader = jose.HeaderKey("kid") | ||
15 | |||
16 | type 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. | ||
30 | func (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. | ||
91 | func (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 @@ | |||
1 | package aws | ||
2 | |||
3 | import ( | ||
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. | ||
23 | var defaultRegion = aws.String("us-east-1") | ||
24 | |||
25 | type Region struct { | ||
26 | Name string | ||
27 | Enabled bool | ||
28 | } | ||
29 | |||
30 | type 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. | ||
38 | type account struct { | ||
39 | AccessKeyId string | ||
40 | SecretAccessKey string | ||
41 | Roles map[string]struct { | ||
42 | ARN string | ||
43 | ExternalId string | ||
44 | } | ||
45 | } | ||
46 | |||
47 | type client struct { | ||
48 | AccessKeyId string | ||
49 | SecretAccessKey string | ||
50 | ARN string | ||
51 | ExternalId string | ||
52 | ConsoleSessionDurationSecs int64 | ||
53 | } | ||
54 | |||
55 | var _ 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. | ||
65 | func 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. | ||
98 | func (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. | ||
129 | func (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. | ||
165 | func (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 @@ | |||
1 | package aws | ||
2 | |||
3 | import ( | ||
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. | ||
9 | func 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 @@ | |||
1 | package web | ||
2 | |||
3 | import ( | ||
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 | |||
28 | func 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 | |||
56 | func 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 | } | ||
@@ -0,0 +1,86 @@ | |||
1 | module code.crute.us/mcrute/cloud-identity-broker | ||
2 | |||
3 | go 1.17 | ||
4 | |||
5 | require ( | ||
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 | |||
20 | require ( | ||
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 | ) | ||
@@ -0,0 +1,899 @@ | |||
1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= | ||
2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= | ||
3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= | ||
4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= | ||
5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= | ||
6 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= | ||
7 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= | ||
8 | cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= | ||
9 | cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= | ||
10 | cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= | ||
11 | cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= | ||
12 | cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= | ||
13 | cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= | ||
14 | cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= | ||
15 | cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= | ||
16 | cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= | ||
17 | cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= | ||
18 | cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= | ||
19 | cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= | ||
20 | cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= | ||
21 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= | ||
22 | cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= | ||
23 | cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= | ||
24 | cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= | ||
25 | cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= | ||
26 | cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= | ||
27 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= | ||
28 | cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= | ||
29 | cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= | ||
30 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= | ||
31 | cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= | ||
32 | cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= | ||
33 | cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= | ||
34 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= | ||
35 | cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= | ||
36 | cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= | ||
37 | cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= | ||
38 | cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= | ||
39 | code.crute.us/mcrute/golib v0.1.1/go.mod h1:VOnYQYqBYC3NUYPKwbzYSHW/BUBBU5RX7Z+A9nlJZUc= | ||
40 | code.crute.us/mcrute/golib v0.3.0 h1:7g45xUf/din4VZkKAIK+bPCfXVTlwnZYS4Jv+/lUQ74= | ||
41 | code.crute.us/mcrute/golib v0.3.0/go.mod h1:VOnYQYqBYC3NUYPKwbzYSHW/BUBBU5RX7Z+A9nlJZUc= | ||
42 | code.crute.us/mcrute/golib/cli v0.1.2 h1:Yeg+8Jcm5FSYxFvebIGGmDJqmaDCgxl6QPb0GTTMXi0= | ||
43 | code.crute.us/mcrute/golib/cli v0.1.2/go.mod h1:qhim2CV3zsMflpCbTMJs7dKnzzVIdBkSvm4jHDyXgik= | ||
44 | code.crute.us/mcrute/golib/db/mongodb v0.2.0 h1:tumWZET3BgkutMWkeLLhIYy0c7dgEHa6mABF6oE1Y1o= | ||
45 | code.crute.us/mcrute/golib/db/mongodb v0.2.0/go.mod h1:JUX7PU8mUu68Y4sOERbZKON+x5A7cIxgxifxpXw//Bs= | ||
46 | code.crute.us/mcrute/golib/echo v0.5.0 h1:M8D69fCopxLee4rTYmPswNe2OVI7x7OmQOsr5P9nyUU= | ||
47 | code.crute.us/mcrute/golib/echo v0.5.0/go.mod h1:rNrjiYlJDwkabv0alUpSIsS/tR6HeBoP90UHaPUiL+Q= | ||
48 | code.crute.us/mcrute/golib/vault v0.1.1/go.mod h1:kr+P3q7WJ/+dKieQJ3ZMccWeWV0M3KyHu6Ofghn0H7U= | ||
49 | code.crute.us/mcrute/golib/vault v0.1.2 h1:L80WffgReTtL8FUV83GRAYjPzhAQo4G+h1y1n3CkZEU= | ||
50 | code.crute.us/mcrute/golib/vault v0.1.2/go.mod h1:kr+P3q7WJ/+dKieQJ3ZMccWeWV0M3KyHu6Ofghn0H7U= | ||
51 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= | ||
52 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= | ||
53 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= | ||
54 | github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= | ||
55 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= | ||
56 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= | ||
57 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= | ||
58 | github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= | ||
59 | github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= | ||
60 | github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= | ||
61 | github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= | ||
62 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= | ||
63 | github.com/armon/go-metrics v0.3.9 h1:O2sNqxBdvq8Eq5xmzljcYzAORli6RWCvEym4cJf9m18= | ||
64 | github.com/armon/go-metrics v0.3.9/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= | ||
65 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= | ||
66 | github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= | ||
67 | github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= | ||
68 | github.com/aws/aws-sdk-go v1.42.4 h1:L3gadqlmmdWCDE7aD52l3A5TKVG9jPBHZG1/65x9GVw= | ||
69 | github.com/aws/aws-sdk-go v1.42.4/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= | ||
70 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= | ||
71 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= | ||
72 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= | ||
73 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= | ||
74 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= | ||
75 | github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= | ||
76 | github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3eG1c= | ||
77 | github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= | ||
78 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= | ||
79 | github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= | ||
80 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= | ||
81 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= | ||
82 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= | ||
83 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= | ||
84 | github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= | ||
85 | github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= | ||
86 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= | ||
87 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= | ||
88 | github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= | ||
89 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= | ||
90 | github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= | ||
91 | github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= | ||
92 | github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= | ||
93 | github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= | ||
94 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= | ||
95 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
96 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||
97 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
98 | github.com/elnormous/contenttype v1.0.0 h1:cTLou7K7uQMsPEmRiTJosAznsPcYuoBmXMrFAf86t2A= | ||
99 | github.com/elnormous/contenttype v1.0.0/go.mod h1:ngVcyGGU8pnn4QJ5sL4StrNgc/wmXZXy5IQSBuHOFPg= | ||
100 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= | ||
101 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= | ||
102 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= | ||
103 | github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= | ||
104 | github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= | ||
105 | github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= | ||
106 | github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= | ||
107 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= | ||
108 | github.com/evanphx/json-patch/v5 v5.5.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= | ||
109 | github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= | ||
110 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= | ||
111 | github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= | ||
112 | github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= | ||
113 | github.com/frankban/quicktest v1.10.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y= | ||
114 | github.com/frankban/quicktest v1.13.0 h1:yNZif1OkDfNoDfb9zZa9aXIpejNR4F23Wely0c+Qdqk= | ||
115 | github.com/frankban/quicktest v1.13.0/go.mod h1:qLE0fzW0VuyUAJgPU19zByoIr0HtCHN/r/VLSOOIySU= | ||
116 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= | ||
117 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= | ||
118 | github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= | ||
119 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= | ||
120 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= | ||
121 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= | ||
122 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= | ||
123 | github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= | ||
124 | github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= | ||
125 | github.com/go-ldap/ldap/v3 v3.1.10/go.mod h1:5Zun81jBTabRaI8lzN7E1JjyEl1g6zI6u9pd8luAK4Q= | ||
126 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= | ||
127 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= | ||
128 | github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= | ||
129 | github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= | ||
130 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= | ||
131 | github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= | ||
132 | github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= | ||
133 | github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= | ||
134 | github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= | ||
135 | github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= | ||
136 | github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= | ||
137 | github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= | ||
138 | github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= | ||
139 | github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= | ||
140 | github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= | ||
141 | github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= | ||
142 | github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= | ||
143 | github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo= | ||
144 | github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk= | ||
145 | github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= | ||
146 | github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= | ||
147 | github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg= | ||
148 | github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= | ||
149 | github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= | ||
150 | github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= | ||
151 | github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= | ||
152 | github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= | ||
153 | github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= | ||
154 | github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= | ||
155 | github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= | ||
156 | github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= | ||
157 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= | ||
158 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= | ||
159 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= | ||
160 | github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= | ||
161 | github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= | ||
162 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= | ||
163 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= | ||
164 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= | ||
165 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= | ||
166 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= | ||
167 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= | ||
168 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= | ||
169 | github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= | ||
170 | github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= | ||
171 | github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= | ||
172 | github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= | ||
173 | github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= | ||
174 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= | ||
175 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= | ||
176 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= | ||
177 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= | ||
178 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= | ||
179 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= | ||
180 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= | ||
181 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= | ||
182 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= | ||
183 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= | ||
184 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= | ||
185 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= | ||
186 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= | ||
187 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= | ||
188 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= | ||
189 | github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= | ||
190 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= | ||
191 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= | ||
192 | github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= | ||
193 | github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= | ||
194 | github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= | ||
195 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= | ||
196 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= | ||
197 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= | ||
198 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= | ||
199 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= | ||
200 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||
201 | github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||
202 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||
203 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||
204 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||
205 | github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||
206 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||
207 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= | ||
208 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||
209 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= | ||
210 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= | ||
211 | github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= | ||
212 | github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= | ||
213 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= | ||
214 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= | ||
215 | github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= | ||
216 | github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= | ||
217 | github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= | ||
218 | github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= | ||
219 | github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= | ||
220 | github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= | ||
221 | github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= | ||
222 | github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= | ||
223 | github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= | ||
224 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= | ||
225 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | ||
226 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= | ||
227 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= | ||
228 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= | ||
229 | github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= | ||
230 | github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= | ||
231 | github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= | ||
232 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= | ||
233 | github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= | ||
234 | github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= | ||
235 | github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= | ||
236 | github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= | ||
237 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= | ||
238 | github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= | ||
239 | github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= | ||
240 | github.com/hashicorp/go-hclog v0.16.2 h1:K4ev2ib4LdQETX5cSZBG0DVLk1jwGqSPXBjdah3veNs= | ||
241 | github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= | ||
242 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= | ||
243 | github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= | ||
244 | github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= | ||
245 | github.com/hashicorp/go-kms-wrapping/entropy v0.1.0/go.mod h1:d1g9WGtAunDNpek8jUIEJnBlbgKS1N2Q61QkHiZyR1g= | ||
246 | github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= | ||
247 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= | ||
248 | github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= | ||
249 | github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= | ||
250 | github.com/hashicorp/go-plugin v1.4.3 h1:DXmvivbWD5qdiBts9TpBC7BYL1Aia5sxbRgQB+v6UZM= | ||
251 | github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= | ||
252 | github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= | ||
253 | github.com/hashicorp/go-retryablehttp v0.6.6 h1:HJunrbHTDDbBb/ay4kxa1n+dLmttUlnP3V9oNE4hmsM= | ||
254 | github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= | ||
255 | github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= | ||
256 | github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= | ||
257 | github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= | ||
258 | github.com/hashicorp/go-secure-stdlib/base62 v0.1.1/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw= | ||
259 | github.com/hashicorp/go-secure-stdlib/mlock v0.1.1 h1:cCRo8gK7oq6A2L6LICkUZ+/a5rLiRXFMf1Qd4xSwxTc= | ||
260 | github.com/hashicorp/go-secure-stdlib/mlock v0.1.1/go.mod h1:zq93CJChV6L9QTfGKtfBxKqD7BqqXx5O04A/ns2p5+I= | ||
261 | github.com/hashicorp/go-secure-stdlib/parseutil v0.1.1 h1:78ki3QBevHwYrVxnyVeaEz+7WtifHhauYF23es/0KlI= | ||
262 | github.com/hashicorp/go-secure-stdlib/parseutil v0.1.1/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= | ||
263 | github.com/hashicorp/go-secure-stdlib/password v0.1.1/go.mod h1:9hH302QllNwu1o2TGYtSk8I8kTAN0ca1EHpwhm5Mmzo= | ||
264 | github.com/hashicorp/go-secure-stdlib/strutil v0.1.1 h1:nd0HIW15E6FG1MsnArYaHfuw9C2zgzM8LxkG5Ty/788= | ||
265 | github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U= | ||
266 | github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.1/go.mod h1:l8slYwnJA26yBz+ErHpp2IRCLr0vuOMGBORIz4rRiAs= | ||
267 | github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= | ||
268 | github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= | ||
269 | github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= | ||
270 | github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= | ||
271 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= | ||
272 | github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= | ||
273 | github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= | ||
274 | github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= | ||
275 | github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E= | ||
276 | github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= | ||
277 | github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= | ||
278 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= | ||
279 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= | ||
280 | github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= | ||
281 | github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= | ||
282 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= | ||
283 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= | ||
284 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= | ||
285 | github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= | ||
286 | github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= | ||
287 | github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= | ||
288 | github.com/hashicorp/vault/api v1.3.0 h1:uDy39PLSvy6gtKyjOCRPizy2QdFiIYSWBR2pxCEzYL8= | ||
289 | github.com/hashicorp/vault/api v1.3.0/go.mod h1:EabNQLI0VWbWoGlA+oBLC8PXmR9D60aUVgQGvangFWQ= | ||
290 | github.com/hashicorp/vault/sdk v0.3.0 h1:kR3dpxNkhh/wr6ycaJYqp6AFT/i2xaftbfnwZduTKEY= | ||
291 | github.com/hashicorp/vault/sdk v0.3.0/go.mod h1:aZ3fNuL5VNydQk8GcLJ2TV8YCRVvyaakYkhZRoVuhj0= | ||
292 | github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= | ||
293 | github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= | ||
294 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= | ||
295 | github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= | ||
296 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= | ||
297 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= | ||
298 | github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= | ||
299 | github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= | ||
300 | github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= | ||
301 | github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= | ||
302 | github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= | ||
303 | github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= | ||
304 | github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= | ||
305 | github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= | ||
306 | github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= | ||
307 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= | ||
308 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= | ||
309 | github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= | ||
310 | github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= | ||
311 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= | ||
312 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= | ||
313 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= | ||
314 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= | ||
315 | github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= | ||
316 | github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= | ||
317 | github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= | ||
318 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= | ||
319 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= | ||
320 | github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= | ||
321 | github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= | ||
322 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= | ||
323 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= | ||
324 | github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= | ||
325 | github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= | ||
326 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= | ||
327 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= | ||
328 | github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= | ||
329 | github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= | ||
330 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= | ||
331 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= | ||
332 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= | ||
333 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= | ||
334 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= | ||
335 | github.com/labstack/echo/v4 v4.6.1 h1:OMVsrnNFzYlGSdaiYGHbgWQnr+JM7NG+B9suCPie14M= | ||
336 | github.com/labstack/echo/v4 v4.6.1/go.mod h1:RnjgMWNDB9g/HucVWhQYNQP9PvbYf6adqftqryo7s9k= | ||
337 | github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= | ||
338 | github.com/labstack/gommon v0.3.1 h1:OomWaJXm7xR6L1HmEtGyQf26TEn7V6X88mktX9kee9o= | ||
339 | github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= | ||
340 | github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= | ||
341 | github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= | ||
342 | github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= | ||
343 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= | ||
344 | github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= | ||
345 | github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= | ||
346 | github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= | ||
347 | github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= | ||
348 | github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs= | ||
349 | github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= | ||
350 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= | ||
351 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= | ||
352 | github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= | ||
353 | github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= | ||
354 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= | ||
355 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= | ||
356 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= | ||
357 | github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= | ||
358 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= | ||
359 | github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= | ||
360 | github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= | ||
361 | github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= | ||
362 | github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= | ||
363 | github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= | ||
364 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= | ||
365 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= | ||
366 | github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= | ||
367 | github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= | ||
368 | github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= | ||
369 | github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= | ||
370 | github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= | ||
371 | github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= | ||
372 | github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= | ||
373 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= | ||
374 | github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= | ||
375 | github.com/mitchellh/mapstructure v1.4.2 h1:6h7AQ0yhTcIsmFmnAwQls75jp2Gzs4iB8W7pjMO+rqo= | ||
376 | github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= | ||
377 | github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= | ||
378 | github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= | ||
379 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= | ||
380 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= | ||
381 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= | ||
382 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= | ||
383 | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= | ||
384 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= | ||
385 | github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= | ||
386 | github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= | ||
387 | github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= | ||
388 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= | ||
389 | github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= | ||
390 | github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= | ||
391 | github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= | ||
392 | github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= | ||
393 | github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI= | ||
394 | github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= | ||
395 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | ||
396 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | ||
397 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= | ||
398 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | ||
399 | github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= | ||
400 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||
401 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||
402 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= | ||
403 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= | ||
404 | github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= | ||
405 | github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= | ||
406 | github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= | ||
407 | github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ= | ||
408 | github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= | ||
409 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= | ||
410 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= | ||
411 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= | ||
412 | github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= | ||
413 | github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= | ||
414 | github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= | ||
415 | github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= | ||
416 | github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= | ||
417 | github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= | ||
418 | github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= | ||
419 | github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= | ||
420 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= | ||
421 | github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= | ||
422 | github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= | ||
423 | github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= | ||
424 | github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= | ||
425 | github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= | ||
426 | github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= | ||
427 | github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= | ||
428 | github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= | ||
429 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= | ||
430 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= | ||
431 | github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= | ||
432 | github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= | ||
433 | github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= | ||
434 | github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= | ||
435 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= | ||
436 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= | ||
437 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= | ||
438 | github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= | ||
439 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= | ||
440 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= | ||
441 | github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= | ||
442 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= | ||
443 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= | ||
444 | github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= | ||
445 | github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= | ||
446 | github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= | ||
447 | github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw= | ||
448 | github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= | ||
449 | github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= | ||
450 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= | ||
451 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= | ||
452 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= | ||
453 | github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= | ||
454 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||
455 | github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= | ||
456 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||
457 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= | ||
458 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= | ||
459 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= | ||
460 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= | ||
461 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||
462 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= | ||
463 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||
464 | github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= | ||
465 | github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= | ||
466 | github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= | ||
467 | github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= | ||
468 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= | ||
469 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= | ||
470 | github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= | ||
471 | github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4= | ||
472 | github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= | ||
473 | github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= | ||
474 | github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= | ||
475 | github.com/xdg-go/scram v1.0.2 h1:akYIkZ28e6A96dkWNJQu3nmCzH3YfwMPQExUYDaRv7w= | ||
476 | github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= | ||
477 | github.com/xdg-go/stringprep v1.0.2 h1:6iq84/ryjjeRmMJwxutI51F2GIPlP5BfTvXHeYjyhBc= | ||
478 | github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= | ||
479 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA= | ||
480 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= | ||
481 | github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | ||
482 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | ||
483 | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | ||
484 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | ||
485 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= | ||
486 | go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= | ||
487 | go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= | ||
488 | go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= | ||
489 | go.mongodb.org/mongo-driver v1.7.4 h1:sllcioag8Mec0LYkftYWq+cKNPIR4Kqq3iv9ZXY0g/E= | ||
490 | go.mongodb.org/mongo-driver v1.7.4/go.mod h1:NqaYOwnXWr5Pm7AOpO5QFxKJ503nbMse/R79oO62zWg= | ||
491 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= | ||
492 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= | ||
493 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= | ||
494 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= | ||
495 | go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= | ||
496 | go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= | ||
497 | go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= | ||
498 | go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= | ||
499 | go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= | ||
500 | go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= | ||
501 | go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= | ||
502 | go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= | ||
503 | go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= | ||
504 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= | ||
505 | golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= | ||
506 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | ||
507 | golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= | ||
508 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | ||
509 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | ||
510 | golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | ||
511 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | ||
512 | golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | ||
513 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | ||
514 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= | ||
515 | golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= | ||
516 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg= | ||
517 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= | ||
518 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= | ||
519 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= | ||
520 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= | ||
521 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= | ||
522 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= | ||
523 | golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= | ||
524 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= | ||
525 | golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= | ||
526 | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= | ||
527 | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= | ||
528 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= | ||
529 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= | ||
530 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= | ||
531 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= | ||
532 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= | ||
533 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= | ||
534 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= | ||
535 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= | ||
536 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= | ||
537 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= | ||
538 | golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= | ||
539 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= | ||
540 | golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= | ||
541 | golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= | ||
542 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= | ||
543 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= | ||
544 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= | ||
545 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= | ||
546 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= | ||
547 | golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= | ||
548 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | ||
549 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | ||
550 | golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | ||
551 | golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | ||
552 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | ||
553 | golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||
554 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||
555 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||
556 | golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||
557 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||
558 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||
559 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||
560 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||
561 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | ||
562 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | ||
563 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | ||
564 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | ||
565 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= | ||
566 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||
567 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||
568 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||
569 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||
570 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||
571 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||
572 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||
573 | golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||
574 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||
575 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||
576 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= | ||
577 | golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= | ||
578 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= | ||
579 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= | ||
580 | golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= | ||
581 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= | ||
582 | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= | ||
583 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= | ||
584 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= | ||
585 | golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= | ||
586 | golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= | ||
587 | golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= | ||
588 | golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= | ||
589 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= | ||
590 | golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= | ||
591 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= | ||
592 | golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= | ||
593 | golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= | ||
594 | golang.org/x/net v0.0.0-20210913180222-943fd674d43e h1:+b/22bPvDYt4NPDcy4xAGCmON713ONAWFeY3Z7I3tR8= | ||
595 | golang.org/x/net v0.0.0-20210913180222-943fd674d43e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= | ||
596 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= | ||
597 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= | ||
598 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= | ||
599 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= | ||
600 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= | ||
601 | golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= | ||
602 | golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= | ||
603 | golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= | ||
604 | golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= | ||
605 | golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= | ||
606 | golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= | ||
607 | golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= | ||
608 | golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= | ||
609 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||
610 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||
611 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||
612 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||
613 | golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||
614 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||
615 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||
616 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||
617 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||
618 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||
619 | golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||
620 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= | ||
621 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||
622 | golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||
623 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||
624 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||
625 | golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||
626 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||
627 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||
628 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||
629 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
630 | golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
631 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
632 | golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
633 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
634 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
635 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
636 | golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
637 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
638 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
639 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
640 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
641 | golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
642 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
643 | golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
644 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
645 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
646 | golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
647 | golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
648 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
649 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
650 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
651 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
652 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
653 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
654 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
655 | golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
656 | golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
657 | golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
658 | golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
659 | golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
660 | golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
661 | golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
662 | golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
663 | golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
664 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
665 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
666 | golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
667 | golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
668 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
669 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
670 | golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
671 | golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
672 | golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
673 | golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
674 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
675 | golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
676 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
677 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||
678 | golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||
679 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||
680 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||
681 | golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||
682 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||
683 | golang.org/x/sys v0.0.0-20211103235746-7861aae1554b h1:1VkfZQv42XQlA/jchYumAnv1UPo6RgF9rJFkTgZIxO4= | ||
684 | golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||
685 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | ||
686 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||
687 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||
688 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||
689 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= | ||
690 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||
691 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||
692 | golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||
693 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||
694 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= | ||
695 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= | ||
696 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | ||
697 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | ||
698 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | ||
699 | golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | ||
700 | golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 h1:Hir2P/De0WpUhtrKGGjvSb2YxUgyZ7EFOSLIcSSpiwE= | ||
701 | golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | ||
702 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||
703 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||
704 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= | ||
705 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= | ||
706 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= | ||
707 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= | ||
708 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= | ||
709 | golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= | ||
710 | golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= | ||
711 | golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= | ||
712 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= | ||
713 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= | ||
714 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= | ||
715 | golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= | ||
716 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= | ||
717 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= | ||
718 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= | ||
719 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | ||
720 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | ||
721 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | ||
722 | golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | ||
723 | golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | ||
724 | golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | ||
725 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | ||
726 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | ||
727 | golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | ||
728 | golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= | ||
729 | golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= | ||
730 | golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= | ||
731 | golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= | ||
732 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= | ||
733 | golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= | ||
734 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= | ||
735 | golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= | ||
736 | golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= | ||
737 | golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= | ||
738 | golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= | ||
739 | golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= | ||
740 | golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= | ||
741 | golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= | ||
742 | golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= | ||
743 | golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= | ||
744 | golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= | ||
745 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= | ||
746 | golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= | ||
747 | golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= | ||
748 | golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= | ||
749 | golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= | ||
750 | golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= | ||
751 | golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= | ||
752 | golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= | ||
753 | golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= | ||
754 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= | ||
755 | golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= | ||
756 | golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= | ||
757 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||
758 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||
759 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||
760 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= | ||
761 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||
762 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= | ||
763 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= | ||
764 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= | ||
765 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= | ||
766 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= | ||
767 | google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= | ||
768 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= | ||
769 | google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= | ||
770 | google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= | ||
771 | google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= | ||
772 | google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= | ||
773 | google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= | ||
774 | google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= | ||
775 | google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= | ||
776 | google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= | ||
777 | google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= | ||
778 | google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= | ||
779 | google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= | ||
780 | google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= | ||
781 | google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= | ||
782 | google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= | ||
783 | google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= | ||
784 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= | ||
785 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= | ||
786 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= | ||
787 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= | ||
788 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= | ||
789 | google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= | ||
790 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= | ||
791 | google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= | ||
792 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= | ||
793 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= | ||
794 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= | ||
795 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= | ||
796 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= | ||
797 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= | ||
798 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= | ||
799 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= | ||
800 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= | ||
801 | google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= | ||
802 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= | ||
803 | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= | ||
804 | google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= | ||
805 | google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= | ||
806 | google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= | ||
807 | google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= | ||
808 | google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= | ||
809 | google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= | ||
810 | google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= | ||
811 | google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= | ||
812 | google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= | ||
813 | google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= | ||
814 | google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= | ||
815 | google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= | ||
816 | google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= | ||
817 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= | ||
818 | google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= | ||
819 | google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= | ||
820 | google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= | ||
821 | google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= | ||
822 | google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= | ||
823 | google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= | ||
824 | google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= | ||
825 | google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= | ||
826 | google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= | ||
827 | google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= | ||
828 | google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= | ||
829 | google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= | ||
830 | google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= | ||
831 | google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= | ||
832 | google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c h1:wtujag7C+4D6KMoulW9YauvK2lgdvCMS260jsqqBXr0= | ||
833 | google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= | ||
834 | google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= | ||
835 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= | ||
836 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= | ||
837 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= | ||
838 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= | ||
839 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= | ||
840 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= | ||
841 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= | ||
842 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= | ||
843 | google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= | ||
844 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= | ||
845 | google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= | ||
846 | google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= | ||
847 | google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= | ||
848 | google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= | ||
849 | google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= | ||
850 | google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= | ||
851 | google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= | ||
852 | google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= | ||
853 | google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= | ||
854 | google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= | ||
855 | google.golang.org/grpc v1.41.0 h1:f+PlOh7QV4iIJkPrx5NQ7qaNGFQ3OTse67yaDHfju4E= | ||
856 | google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= | ||
857 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= | ||
858 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= | ||
859 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= | ||
860 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= | ||
861 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= | ||
862 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= | ||
863 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= | ||
864 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= | ||
865 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= | ||
866 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= | ||
867 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= | ||
868 | google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= | ||
869 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= | ||
870 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= | ||
871 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
872 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
873 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
874 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= | ||
875 | gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= | ||
876 | gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= | ||
877 | gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= | ||
878 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||
879 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||
880 | gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||
881 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||
882 | gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||
883 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||
884 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||
885 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= | ||
886 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= | ||
887 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||
888 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= | ||
889 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||
890 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= | ||
891 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= | ||
892 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= | ||
893 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= | ||
894 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= | ||
895 | honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= | ||
896 | honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= | ||
897 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= | ||
898 | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= | ||
899 | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= | ||
@@ -0,0 +1,45 @@ | |||
1 | package main | ||
2 | |||
3 | import ( | ||
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 | ||
18 | var embeddedTemplates embed.FS | ||
19 | |||
20 | func 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> | ||