mirror of
https://github.com/pomerium/pomerium.git
synced 2025-04-28 18:06:34 +02:00
pomerium-cli k8s exec-credential (#1073)
* wip * wip * remove dead code * add logging about errors for caching credentials * rename subcommand
This commit is contained in:
parent
ee1f9093ee
commit
5df10d1539
6 changed files with 445 additions and 98 deletions
96
cmd/pomerium-cli/cache.go
Normal file
96
cmd/pomerium-cli/cache.go
Normal file
|
@ -0,0 +1,96 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
func configHome() string {
|
||||
cfgDir, err := os.UserConfigDir()
|
||||
if err != nil {
|
||||
fatalf("error getting user config dir: %v", err)
|
||||
}
|
||||
|
||||
ch := filepath.Join(cfgDir, "pomerium-cli")
|
||||
err = os.MkdirAll(ch, 0755)
|
||||
if err != nil {
|
||||
fatalf("error creating user config dir: %v", err)
|
||||
}
|
||||
|
||||
return ch
|
||||
}
|
||||
|
||||
func cachePath() string {
|
||||
return filepath.Join(configHome(), "cache", "exec-credential")
|
||||
}
|
||||
|
||||
func cachedCredentialPath(serverURL string) string {
|
||||
h := sha256.New()
|
||||
_, _ = h.Write([]byte(serverURL))
|
||||
id := hex.EncodeToString(h.Sum(nil))
|
||||
return filepath.Join(cachePath(), id+".json")
|
||||
}
|
||||
|
||||
func loadCachedCredential(serverURL string) *ExecCredential {
|
||||
fn := cachedCredentialPath(serverURL)
|
||||
|
||||
f, err := os.Open(fn)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var creds ExecCredential
|
||||
err = json.NewDecoder(f).Decode(&creds)
|
||||
if err != nil {
|
||||
_ = os.Remove(fn)
|
||||
return nil
|
||||
}
|
||||
|
||||
if creds.Status == nil {
|
||||
_ = os.Remove(fn)
|
||||
return nil
|
||||
}
|
||||
|
||||
ts := creds.Status.ExpirationTimestamp
|
||||
if ts.IsZero() || ts.Before(time.Now()) {
|
||||
_ = os.Remove(fn)
|
||||
return nil
|
||||
}
|
||||
|
||||
return &creds
|
||||
}
|
||||
|
||||
func saveCachedCredential(serverURL string, creds *ExecCredential) {
|
||||
fn := cachedCredentialPath(serverURL)
|
||||
|
||||
err := os.MkdirAll(filepath.Dir(fn), 0755)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to create cache directory: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
f, err := os.Create(fn)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to create cache file: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = json.NewEncoder(f).Encode(creds)
|
||||
if err != nil {
|
||||
_ = f.Close()
|
||||
fmt.Fprintf(os.Stderr, "failed to encode credentials to cache file: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = f.Close()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to close cache file: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
|
@ -3,14 +3,15 @@ package main
|
|||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/encoding/jws"
|
||||
"github.com/spf13/cobra"
|
||||
"gopkg.in/square/go-jose.v2/jwt"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/encoding/jws"
|
||||
)
|
||||
|
||||
type stringSlice []string
|
||||
|
@ -29,6 +30,10 @@ func (i *stringSlice) Set(value string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (i *stringSlice) Type() string {
|
||||
return "slice"
|
||||
}
|
||||
|
||||
type serviceAccount struct {
|
||||
// Standard claims (as specified in RFC 7519).
|
||||
jwt.Claims
|
||||
|
@ -40,104 +45,74 @@ type serviceAccount struct {
|
|||
ImpersonateGroups []string `json:"impersonate_groups,omitempty"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
if err := run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "\n⛔️%s\n\n", err)
|
||||
printHelp(flags)
|
||||
os.Exit(1)
|
||||
}
|
||||
os.Exit(0)
|
||||
var serviceAccountOptions struct {
|
||||
aud stringSlice
|
||||
groups stringSlice
|
||||
impersonateGroups stringSlice
|
||||
expiry time.Duration
|
||||
serviceAccount serviceAccount
|
||||
}
|
||||
|
||||
var flags *flag.FlagSet
|
||||
|
||||
func run() error {
|
||||
var sa serviceAccount
|
||||
|
||||
// temporary variables we will use to hydrate our service account
|
||||
// struct from basic types pulled in from our flags
|
||||
var (
|
||||
aud stringSlice
|
||||
groups stringSlice
|
||||
impersonateGroups stringSlice
|
||||
expiry time.Duration
|
||||
)
|
||||
|
||||
flags = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
|
||||
flags.StringVar(&sa.Email, "email", "", "Email")
|
||||
flags.StringVar(&sa.ImpersonateEmail, "impersonate_email", "", "Impersonation Email (optional)")
|
||||
flags.StringVar(&sa.Issuer, "iss", "", "Issuing Server (e.g authenticate.int.pomerium.io)")
|
||||
flags.StringVar(&sa.Subject, "sub", "", "Subject (typically User's GUID)")
|
||||
flags.StringVar(&sa.User, "user", "", "User (typically User's GUID)")
|
||||
flags.Var(&aud, "aud", "Audience (e.g. httpbin.int.pomerium.io,prometheus.int.pomerium.io)")
|
||||
flags.Var(&groups, "groups", "Groups (e.g. admins@pomerium.io,users@pomerium.io)")
|
||||
flags.Var(&impersonateGroups, "impersonate_groups", "Impersonation Groups (optional)")
|
||||
flags.DurationVar(&expiry, "expiry", time.Hour, "Expiry")
|
||||
if err := flags.Parse(os.Args[1:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// hydrate our session
|
||||
sa.Audience = jwt.Audience(aud)
|
||||
sa.Groups = []string(groups)
|
||||
sa.ImpersonateGroups = []string(impersonateGroups)
|
||||
sa.Expiry = jwt.NewNumericDate(time.Now().Add(expiry))
|
||||
sa.IssuedAt = jwt.NewNumericDate(time.Now())
|
||||
sa.NotBefore = jwt.NewNumericDate(time.Now())
|
||||
|
||||
var sharedKey string
|
||||
args := flags.Args()
|
||||
if len(args) == 1 {
|
||||
sharedKey = args[0]
|
||||
} else {
|
||||
fmt.Print("Enter base64 encoded shared key >")
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
scanner.Scan()
|
||||
sharedKey = scanner.Text()
|
||||
}
|
||||
|
||||
if sharedKey == "" {
|
||||
return errors.New("shared key required")
|
||||
}
|
||||
|
||||
if sa.Email == "" {
|
||||
return errors.New("email is required")
|
||||
}
|
||||
|
||||
if len(sa.Audience) == 0 {
|
||||
return errors.New("aud is required")
|
||||
}
|
||||
|
||||
if sa.Issuer == "" {
|
||||
return errors.New("iss is required")
|
||||
}
|
||||
encoder, err := jws.NewHS256Signer([]byte(sharedKey), sa.Issuer)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bad shared key: %w", err)
|
||||
}
|
||||
raw, err := encoder.Marshal(sa)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bad encode: %w", err)
|
||||
}
|
||||
fmt.Fprintf(os.Stdout, "%s", raw)
|
||||
return nil
|
||||
func init() {
|
||||
flags := serviceAccountCmd.PersistentFlags()
|
||||
flags.StringVar(&serviceAccountOptions.serviceAccount.Email, "email", "", "Email")
|
||||
flags.StringVar(&serviceAccountOptions.serviceAccount.ImpersonateEmail, "impersonate_email", "", "Impersonation Email (optional)")
|
||||
flags.StringVar(&serviceAccountOptions.serviceAccount.Issuer, "iss", "", "Issuing Server (e.g authenticate.int.pomerium.io)")
|
||||
flags.StringVar(&serviceAccountOptions.serviceAccount.Subject, "sub", "", "Subject (typically User's GUID)")
|
||||
flags.StringVar(&serviceAccountOptions.serviceAccount.User, "user", "", "User (typically User's GUID)")
|
||||
flags.Var(&serviceAccountOptions.aud, "aud", "Audience (e.g. httpbin.int.pomerium.io,prometheus.int.pomerium.io)")
|
||||
flags.Var(&serviceAccountOptions.groups, "groups", "Groups (e.g. admins@pomerium.io,users@pomerium.io)")
|
||||
flags.Var(&serviceAccountOptions.impersonateGroups, "impersonate_groups", "Impersonation Groups (optional)")
|
||||
flags.DurationVar(&serviceAccountOptions.expiry, "expiry", time.Hour, "Expiry")
|
||||
rootCmd.AddCommand(serviceAccountCmd)
|
||||
}
|
||||
|
||||
func printHelp(fs *flag.FlagSet) {
|
||||
fmt.Fprintf(os.Stderr, strings.TrimSpace(help)+"\n\n", os.Args[0])
|
||||
fs.PrintDefaults()
|
||||
var serviceAccountCmd = &cobra.Command{
|
||||
Use: "service-account",
|
||||
Short: "generates a pomerium service account from a shared key.",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
// hydrate our session
|
||||
serviceAccountOptions.serviceAccount.Audience = jwt.Audience(serviceAccountOptions.aud)
|
||||
serviceAccountOptions.serviceAccount.Groups = []string(serviceAccountOptions.groups)
|
||||
serviceAccountOptions.serviceAccount.ImpersonateGroups = []string(serviceAccountOptions.impersonateGroups)
|
||||
serviceAccountOptions.serviceAccount.Expiry = jwt.NewNumericDate(time.Now().Add(serviceAccountOptions.expiry))
|
||||
serviceAccountOptions.serviceAccount.IssuedAt = jwt.NewNumericDate(time.Now())
|
||||
serviceAccountOptions.serviceAccount.NotBefore = jwt.NewNumericDate(time.Now())
|
||||
|
||||
var sharedKey string
|
||||
if len(args) == 1 {
|
||||
sharedKey = args[0]
|
||||
} else {
|
||||
fmt.Print("Enter base64 encoded shared key >")
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
scanner.Scan()
|
||||
sharedKey = scanner.Text()
|
||||
}
|
||||
|
||||
if sharedKey == "" {
|
||||
return errors.New("shared key required")
|
||||
}
|
||||
|
||||
if serviceAccountOptions.serviceAccount.Email == "" {
|
||||
return errors.New("email is required")
|
||||
}
|
||||
|
||||
if len(serviceAccountOptions.serviceAccount.Audience) == 0 {
|
||||
return errors.New("aud is required")
|
||||
}
|
||||
|
||||
if serviceAccountOptions.serviceAccount.Issuer == "" {
|
||||
return errors.New("iss is required")
|
||||
}
|
||||
encoder, err := jws.NewHS256Signer([]byte(sharedKey), serviceAccountOptions.serviceAccount.Issuer)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bad shared key: %w", err)
|
||||
}
|
||||
raw, err := encoder.Marshal(serviceAccountOptions.serviceAccount)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bad encode: %w", err)
|
||||
}
|
||||
fmt.Fprintf(os.Stdout, "%s", raw)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
const help = `
|
||||
pomerium-cli generates a pomerium service account from a shared key.
|
||||
|
||||
Usage: %[1]s [flags] [base64'd shared secret setting]
|
||||
|
||||
For additional help see:
|
||||
|
||||
https://www.pomerium.io
|
||||
https://jwt.io/
|
||||
|
||||
Flags:
|
||||
|
||||
`
|
||||
|
|
244
cmd/pomerium-cli/kubernetes.go
Normal file
244
cmd/pomerium-cli/kubernetes.go
Normal file
|
@ -0,0 +1,244 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/skratchdot/open-golang/open"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/square/go-jose/jwt"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
func init() {
|
||||
kubernetesCmd.AddCommand(kubernetesExecCredentialCmd)
|
||||
rootCmd.AddCommand(kubernetesCmd)
|
||||
}
|
||||
|
||||
var kubernetesCmd = &cobra.Command{
|
||||
Use: "k8s",
|
||||
}
|
||||
|
||||
var kubernetesExecCredentialCmd = &cobra.Command{
|
||||
Use: "exec-credential",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if !terminal.IsTerminal(int(os.Stdin.Fd())) {
|
||||
return fmt.Errorf("only interactive sessions are supported")
|
||||
}
|
||||
|
||||
if len(args) < 1 {
|
||||
return fmt.Errorf("server url is required")
|
||||
}
|
||||
|
||||
serverURL, err := url.Parse(args[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid server url: %v", err)
|
||||
}
|
||||
|
||||
creds := loadCachedCredential(serverURL.String())
|
||||
if creds != nil {
|
||||
printCreds(creds)
|
||||
return nil
|
||||
}
|
||||
|
||||
li, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
fatalf("failed to start listener: %v", err)
|
||||
}
|
||||
defer li.Close()
|
||||
|
||||
incomingJWT := make(chan string)
|
||||
|
||||
eg, ctx := errgroup.WithContext(context.Background())
|
||||
eg.Go(func() error {
|
||||
return runHTTPServer(ctx, li, incomingJWT)
|
||||
})
|
||||
eg.Go(func() error {
|
||||
return runOpenBrowser(ctx, li, serverURL)
|
||||
})
|
||||
eg.Go(func() error {
|
||||
return runHandleJWT(ctx, serverURL, incomingJWT)
|
||||
})
|
||||
err = eg.Wait()
|
||||
if err != nil {
|
||||
fatalf("%s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func runHTTPServer(ctx context.Context, li net.Listener, incomingJWT chan string) error {
|
||||
var srv *http.Server
|
||||
srv = &http.Server{
|
||||
BaseContext: func(li net.Listener) context.Context {
|
||||
return ctx
|
||||
},
|
||||
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
jwt := r.FormValue("pomerium_jwt")
|
||||
if jwt == "" {
|
||||
http.Error(w, "not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
incomingJWT <- jwt
|
||||
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
io.WriteString(w, "login complete, you may close this page")
|
||||
|
||||
go func() { _ = srv.Shutdown(ctx) }()
|
||||
}),
|
||||
}
|
||||
err := srv.Serve(li)
|
||||
if err == http.ErrServerClosed {
|
||||
err = nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func runOpenBrowser(ctx context.Context, li net.Listener, serverURL *url.URL) error {
|
||||
dst := serverURL.ResolveReference(&url.URL{
|
||||
Path: "/.pomerium/api/v1/login",
|
||||
RawQuery: url.Values{
|
||||
"pomerium_redirect_uri": {fmt.Sprintf("http://%s", li.Addr().String())},
|
||||
}.Encode(),
|
||||
})
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", dst.String(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get login url: %w", err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode/100 != 2 {
|
||||
return fmt.Errorf("failed to get login url: %s", res.Status)
|
||||
}
|
||||
|
||||
bs, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read login url: %w", err)
|
||||
}
|
||||
|
||||
return open.Run(string(bs))
|
||||
}
|
||||
|
||||
func runHandleJWT(ctx context.Context, serverURL *url.URL, incomingJWT chan string) error {
|
||||
var rawjwt string
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case rawjwt = <-incomingJWT:
|
||||
}
|
||||
|
||||
creds, err := parseToken(rawjwt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
saveCachedCredential(serverURL.String(), creds)
|
||||
printCreds(creds)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseToken(rawjwt string) (*ExecCredential, error) {
|
||||
tok, err := jwt.ParseSigned(rawjwt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var claims struct {
|
||||
Exp int64 `json:"exp"`
|
||||
}
|
||||
err = tok.UnsafeClaimsWithoutVerification(&claims)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
expiresAt := time.Unix(claims.Exp, 0)
|
||||
if expiresAt.IsZero() {
|
||||
expiresAt = time.Now().Add(time.Minute)
|
||||
}
|
||||
|
||||
return &ExecCredential{
|
||||
TypeMeta: TypeMeta{
|
||||
APIVersion: "client.authentication.k8s.io/v1beta1",
|
||||
Kind: "ExecCredential",
|
||||
},
|
||||
Status: &ExecCredentialStatus{
|
||||
ExpirationTimestamp: time.Now().Add(time.Second * 10),
|
||||
Token: "Pomerium-" + rawjwt,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func printCreds(creds *ExecCredential) {
|
||||
bs, err := json.Marshal(creds)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to encode credentials: %v\n", err)
|
||||
}
|
||||
fmt.Println(string(bs))
|
||||
}
|
||||
|
||||
// TypeMeta describes an individual object in an API response or request
|
||||
// with strings representing the type of the object and its API schema version.
|
||||
// Structures that are versioned or persisted should inline TypeMeta.
|
||||
//
|
||||
// +k8s:deepcopy-gen=false
|
||||
type TypeMeta struct {
|
||||
// Kind is a string value representing the REST resource this object represents.
|
||||
// Servers may infer this from the endpoint the client submits requests to.
|
||||
// Cannot be updated.
|
||||
// In CamelCase.
|
||||
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
// +optional
|
||||
Kind string `json:"kind,omitempty" protobuf:"bytes,1,opt,name=kind"`
|
||||
|
||||
// APIVersion defines the versioned schema of this representation of an object.
|
||||
// Servers should convert recognized schemas to the latest internal value, and
|
||||
// may reject unrecognized values.
|
||||
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
// +optional
|
||||
APIVersion string `json:"apiVersion,omitempty" protobuf:"bytes,2,opt,name=apiVersion"`
|
||||
}
|
||||
|
||||
// ExecCredential is used by exec-based plugins to communicate credentials to
|
||||
// HTTP transports.
|
||||
type ExecCredential struct {
|
||||
TypeMeta `json:",inline"`
|
||||
|
||||
// Status is filled in by the plugin and holds the credentials that the transport
|
||||
// should use to contact the API.
|
||||
// +optional
|
||||
Status *ExecCredentialStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// ExecCredentialStatus holds credentials for the transport to use.
|
||||
//
|
||||
// Token and ClientKeyData are sensitive fields. This data should only be
|
||||
// transmitted in-memory between client and exec plugin process. Exec plugin
|
||||
// itself should at least be protected via file permissions.
|
||||
type ExecCredentialStatus struct {
|
||||
// ExpirationTimestamp indicates a time when the provided credentials expire.
|
||||
// +optional
|
||||
ExpirationTimestamp time.Time `json:"expirationTimestamp,omitempty"`
|
||||
// Token is a bearer token used by the client for request authentication.
|
||||
Token string `json:"token,omitempty"`
|
||||
// PEM-encoded client TLS certificates (including intermediates, if any).
|
||||
ClientCertificateData string `json:"clientCertificateData,omitempty"`
|
||||
// PEM-encoded private key for the above certificate.
|
||||
ClientKeyData string `json:"clientKeyData,omitempty"`
|
||||
}
|
24
cmd/pomerium-cli/main.go
Normal file
24
cmd/pomerium-cli/main.go
Normal file
|
@ -0,0 +1,24 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "pomerium-cli",
|
||||
}
|
||||
|
||||
func main() {
|
||||
err := rootCmd.Execute()
|
||||
if err != nil {
|
||||
fatalf("%s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func fatalf(msg string, args ...interface{}) {
|
||||
fmt.Fprintf(os.Stderr, msg+"\n", args...)
|
||||
os.Exit(1)
|
||||
}
|
3
go.mod
3
go.mod
|
@ -44,11 +44,14 @@ require (
|
|||
github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563 // indirect
|
||||
github.com/rs/cors v1.7.0
|
||||
github.com/rs/zerolog v1.19.0
|
||||
github.com/skratchdot/open-golang v0.0.0-20160302144031-75fb7ed4208c
|
||||
github.com/spf13/afero v1.2.2 // indirect
|
||||
github.com/spf13/cast v1.3.1 // indirect
|
||||
github.com/spf13/cobra v0.0.0-20181021141114-fe5e611709b0
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/viper v1.7.0
|
||||
github.com/square/go-jose v2.5.1+incompatible
|
||||
github.com/stretchr/testify v1.6.1
|
||||
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80
|
||||
github.com/uber/jaeger-client-go v2.20.1+incompatible // indirect
|
||||
|
|
5
go.sum
5
go.sum
|
@ -283,6 +283,7 @@ github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
|||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df/go.mod h1:QMZY7/J/KSQEhKWFeDesPjMj+wCHReeknARU3wqlyN4=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
|
@ -468,6 +469,7 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
|
|||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/skratchdot/open-golang v0.0.0-20160302144031-75fb7ed4208c h1:fyKiXKO1/I/B6Y2U8T7WdQGWzwehOuGIrljPtt7YTTI=
|
||||
github.com/skratchdot/open-golang v0.0.0-20160302144031-75fb7ed4208c/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
|
@ -482,6 +484,7 @@ github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTd
|
|||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
|
||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.0-20181021141114-fe5e611709b0 h1:BgSbPgT2Zu8hDen1jJDGLWO8voaSRVrwsk18Q/uSh5M=
|
||||
github.com/spf13/cobra v0.0.0-20181021141114-fe5e611709b0/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||
|
@ -492,6 +495,8 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
|||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM=
|
||||
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
|
||||
github.com/square/go-jose v2.5.1+incompatible h1:FC+BwI9FzJZWpKaE0yUhFNbp/CyFHndARzuGVME/LGk=
|
||||
github.com/square/go-jose v2.5.1+incompatible/go.mod h1:7MxpAF/1WTVUu8Am+T5kNy+t0902CaLWM4Z745MkOa8=
|
||||
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
|
|
Loading…
Add table
Reference in a new issue