mirror of
https://github.com/pomerium/pomerium.git
synced 2025-05-07 14:26:01 +02:00
169 lines
4.3 KiB
Go
169 lines
4.3 KiB
Go
package main
|
||
|
||
import (
|
||
"bufio"
|
||
"encoding/base64"
|
||
"encoding/json"
|
||
"errors"
|
||
"flag"
|
||
"fmt"
|
||
"os"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/fatih/color"
|
||
"gopkg.in/square/go-jose.v2"
|
||
"gopkg.in/square/go-jose.v2/jwt"
|
||
)
|
||
|
||
type stringSlice []string
|
||
|
||
func (i *stringSlice) String() string {
|
||
return fmt.Sprint(*i)
|
||
}
|
||
|
||
func (i *stringSlice) Set(value string) error {
|
||
if len(*i) > 0 {
|
||
return errors.New("already set")
|
||
}
|
||
for _, dt := range strings.Split(value, ",") {
|
||
*i = append(*i, dt)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
type serviceAccount struct {
|
||
// Standard claims (as specified in RFC 7519).
|
||
jwt.Claims
|
||
// Pomerium claims (not standard claims)
|
||
Email string `json:"email"`
|
||
Groups []string `json:"groups,omitempty"`
|
||
User string `json:"user,omitempty"`
|
||
ImpersonateEmail string `json:"impersonate_email,omitempty"`
|
||
ImpersonateGroups []string `json:"impersonate_groups,omitempty"`
|
||
}
|
||
|
||
func main() {
|
||
if err := run(); err != nil {
|
||
fmt.Fprintf(os.Stderr, color.RedString("\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
|
||
groups stringSlice
|
||
impersonateGroups stringSlice
|
||
expiry time.Duration
|
||
)
|
||
|
||
// set our JWT claims from the supplied CLI flags
|
||
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")
|
||
|
||
// hydrate the sessions non-primate types
|
||
if err := flags.Parse(os.Args[1:]); err != nil {
|
||
return err
|
||
}
|
||
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())
|
||
// why not be pretty?
|
||
c := color.New(color.FgGreen)
|
||
// check that we've got our shared key to sign our jwt
|
||
var sharedKey string
|
||
args := flags.Args()
|
||
if len(args) == 1 {
|
||
sharedKey = args[0]
|
||
} else {
|
||
if _, err := c.Println("Enter base64 encoded shared key >"); err != nil {
|
||
return err
|
||
}
|
||
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")
|
||
}
|
||
|
||
decodedKey, err := base64.StdEncoding.DecodeString(sharedKey)
|
||
if err != nil {
|
||
return fmt.Errorf("shared key not base64: %w", err)
|
||
}
|
||
|
||
signer, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.HS256, Key: decodedKey}, nil)
|
||
if err != nil {
|
||
return fmt.Errorf("bad shared key: %w", err)
|
||
}
|
||
raw, err := jwt.Signed(signer).Claims(sa).CompactSerialize()
|
||
if err != nil {
|
||
return fmt.Errorf("couldn't sign jwt: %w", err)
|
||
}
|
||
saJSON, err := json.MarshalIndent(sa, "", " ")
|
||
if err != nil {
|
||
return fmt.Errorf("couldn't pretty print jwt: %w", err)
|
||
}
|
||
if _, err := c.Println("Service Account"); err != nil {
|
||
return err
|
||
}
|
||
fmt.Fprintf(os.Stdout, "%s\n\n", saJSON)
|
||
if _, err := c.Println("JWT 🍪"); err != nil {
|
||
return err
|
||
}
|
||
fmt.Fprintf(os.Stdout, "%s\n\n", raw)
|
||
|
||
return nil
|
||
}
|
||
|
||
func printHelp(fs *flag.FlagSet) {
|
||
fmt.Fprintf(os.Stderr, strings.TrimSpace(help)+"\n\n", os.Args[0])
|
||
fs.PrintDefaults()
|
||
}
|
||
|
||
const help = `
|
||
pomerium-sa 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:
|
||
|
||
`
|