mirror of
https://github.com/pomerium/pomerium.git
synced 2025-04-30 10:56:28 +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 (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pomerium/pomerium/internal/encoding/jws"
|
"github.com/spf13/cobra"
|
||||||
"gopkg.in/square/go-jose.v2/jwt"
|
"gopkg.in/square/go-jose.v2/jwt"
|
||||||
|
|
||||||
|
"github.com/pomerium/pomerium/internal/encoding/jws"
|
||||||
)
|
)
|
||||||
|
|
||||||
type stringSlice []string
|
type stringSlice []string
|
||||||
|
@ -29,6 +30,10 @@ func (i *stringSlice) Set(value string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *stringSlice) Type() string {
|
||||||
|
return "slice"
|
||||||
|
}
|
||||||
|
|
||||||
type serviceAccount struct {
|
type serviceAccount struct {
|
||||||
// Standard claims (as specified in RFC 7519).
|
// Standard claims (as specified in RFC 7519).
|
||||||
jwt.Claims
|
jwt.Claims
|
||||||
|
@ -40,53 +45,41 @@ type serviceAccount struct {
|
||||||
ImpersonateGroups []string `json:"impersonate_groups,omitempty"`
|
ImpersonateGroups []string `json:"impersonate_groups,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
var serviceAccountOptions struct {
|
||||||
if err := run(); err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "\n⛔️%s\n\n", err)
|
|
||||||
printHelp(flags)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
aud stringSlice
|
||||||
groups stringSlice
|
groups stringSlice
|
||||||
impersonateGroups stringSlice
|
impersonateGroups stringSlice
|
||||||
expiry time.Duration
|
expiry time.Duration
|
||||||
)
|
serviceAccount serviceAccount
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
// hydrate our session
|
||||||
sa.Audience = jwt.Audience(aud)
|
serviceAccountOptions.serviceAccount.Audience = jwt.Audience(serviceAccountOptions.aud)
|
||||||
sa.Groups = []string(groups)
|
serviceAccountOptions.serviceAccount.Groups = []string(serviceAccountOptions.groups)
|
||||||
sa.ImpersonateGroups = []string(impersonateGroups)
|
serviceAccountOptions.serviceAccount.ImpersonateGroups = []string(serviceAccountOptions.impersonateGroups)
|
||||||
sa.Expiry = jwt.NewNumericDate(time.Now().Add(expiry))
|
serviceAccountOptions.serviceAccount.Expiry = jwt.NewNumericDate(time.Now().Add(serviceAccountOptions.expiry))
|
||||||
sa.IssuedAt = jwt.NewNumericDate(time.Now())
|
serviceAccountOptions.serviceAccount.IssuedAt = jwt.NewNumericDate(time.Now())
|
||||||
sa.NotBefore = jwt.NewNumericDate(time.Now())
|
serviceAccountOptions.serviceAccount.NotBefore = jwt.NewNumericDate(time.Now())
|
||||||
|
|
||||||
var sharedKey string
|
var sharedKey string
|
||||||
args := flags.Args()
|
|
||||||
if len(args) == 1 {
|
if len(args) == 1 {
|
||||||
sharedKey = args[0]
|
sharedKey = args[0]
|
||||||
} else {
|
} else {
|
||||||
|
@ -100,44 +93,26 @@ func run() error {
|
||||||
return errors.New("shared key required")
|
return errors.New("shared key required")
|
||||||
}
|
}
|
||||||
|
|
||||||
if sa.Email == "" {
|
if serviceAccountOptions.serviceAccount.Email == "" {
|
||||||
return errors.New("email is required")
|
return errors.New("email is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(sa.Audience) == 0 {
|
if len(serviceAccountOptions.serviceAccount.Audience) == 0 {
|
||||||
return errors.New("aud is required")
|
return errors.New("aud is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
if sa.Issuer == "" {
|
if serviceAccountOptions.serviceAccount.Issuer == "" {
|
||||||
return errors.New("iss is required")
|
return errors.New("iss is required")
|
||||||
}
|
}
|
||||||
encoder, err := jws.NewHS256Signer([]byte(sharedKey), sa.Issuer)
|
encoder, err := jws.NewHS256Signer([]byte(sharedKey), serviceAccountOptions.serviceAccount.Issuer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("bad shared key: %w", err)
|
return fmt.Errorf("bad shared key: %w", err)
|
||||||
}
|
}
|
||||||
raw, err := encoder.Marshal(sa)
|
raw, err := encoder.Marshal(serviceAccountOptions.serviceAccount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("bad encode: %w", err)
|
return fmt.Errorf("bad encode: %w", err)
|
||||||
}
|
}
|
||||||
fmt.Fprintf(os.Stdout, "%s", raw)
|
fmt.Fprintf(os.Stdout, "%s", raw)
|
||||||
return nil
|
return nil
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func printHelp(fs *flag.FlagSet) {
|
|
||||||
fmt.Fprintf(os.Stderr, strings.TrimSpace(help)+"\n\n", os.Args[0])
|
|
||||||
fs.PrintDefaults()
|
|
||||||
}
|
|
||||||
|
|
||||||
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/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563 // indirect
|
||||||
github.com/rs/cors v1.7.0
|
github.com/rs/cors v1.7.0
|
||||||
github.com/rs/zerolog v1.19.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/afero v1.2.2 // indirect
|
||||||
github.com/spf13/cast v1.3.1 // 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/jwalterweatherman v1.1.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/spf13/viper v1.7.0
|
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/stretchr/testify v1.6.1
|
||||||
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80
|
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80
|
||||||
github.com/uber/jaeger-client-go v2.20.1+incompatible // indirect
|
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/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/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/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/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/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=
|
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.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
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/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 h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
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.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||||
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
|
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/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/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.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||||
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
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/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 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM=
|
||||||
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
|
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/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.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.1.1/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