blob: d2bebca49f24304e6de3e1d219e50fd9af5104b8 [file] [log] [blame]
package main
import (
"bytes"
"errors"
"fmt"
"io"
"os"
"os/user"
"time"
"veyron.io/veyron/veyron2"
"veyron.io/veyron/veyron2/rt"
"veyron.io/veyron/veyron2/security"
"veyron.io/veyron/veyron2/vdl/vdlutil"
"veyron.io/veyron/veyron/lib/cmdline"
_ "veyron.io/veyron/veyron/profiles"
vsecurity "veyron.io/veyron/veyron/security"
"veyron.io/veyron/veyron/services/identity"
"veyron.io/veyron/veyron/services/identity/util"
)
const VEYRON_CREDENTIALS = "VEYRON_CREDENTIALS"
var (
// Flags for the "blessself" command
flagBlessSelfFor time.Duration
// Flags for the "bless" command
flagBlessFor time.Duration
flagBlessWith string
// Flags for the "seekblessings" command
flagSeekBlessingsFrom string
flagSeekBlessingsSetDefault bool
flagSeekBlessingsForPeer string
// Flag for the create command
flagCreateOverwrite bool
// Flags common to many commands
flagAddToRoots bool
cmdDump = &cmdline.Command{
Name: "dump",
Short: "Dump out information about the principal",
Long: `
Prints out information about the principal specified by the environment
(VEYRON_CREDENTIALS) that this tool is running in.
`,
Run: func(cmd *cmdline.Command, args []string) error {
p, err := principal()
if err != nil {
return err
}
fmt.Printf("Public key : %v\n", p.PublicKey())
fmt.Println("---------------- BlessingStore ----------------")
fmt.Printf("%v", p.BlessingStore().DebugString())
fmt.Println("---------------- BlessingRoots ----------------")
fmt.Printf("%v", p.Roots().DebugString())
return nil
},
}
cmdDumpBlessings = &cmdline.Command{
Name: "dumpblessings",
Short: "Dump out information about the provided blessings",
Long: `
Prints out information about the blessings (typically obtained from this tool)
encoded in the provided file.
`,
ArgsName: "<file>",
ArgsLong: `
<file> is the path to a file containing blessings typically obtained from
this tool. - is used for STDIN.
`,
Run: func(cmd *cmdline.Command, args []string) error {
if len(args) != 1 {
return fmt.Errorf("requires exactly one argument, <file>, provided %d", len(args))
}
blessings, err := decodeBlessings(args[0])
if err != nil {
return fmt.Errorf("failed to decode provided blessings: %v", err)
}
wire := security.MarshalBlessings(blessings)
fmt.Printf("Blessings : %v\n", blessings)
fmt.Printf("PublicKey : %v\n", blessings.PublicKey())
fmt.Printf("Certificate chains : %d\n", len(wire.CertificateChains))
for idx, chain := range wire.CertificateChains {
fmt.Printf("Chain #%d (%d certificates). Root certificate public key: %v\n", idx, len(chain), rootkey(chain))
for certidx, cert := range chain {
fmt.Printf(" Certificate #%d: %v with ", certidx, cert.Extension)
switch n := len(cert.Caveats); n {
case 1:
fmt.Printf("1 caveat")
default:
fmt.Printf("%d caveats", n)
}
fmt.Println("")
for cavidx, cav := range cert.Caveats {
fmt.Printf(" (%d) %v\n", cavidx, &cav)
}
}
}
return nil
},
}
cmdBlessSelf = &cmdline.Command{
Name: "blessself",
Short: "Generate a self-signed blessing",
Long: `
Returns a blessing with name <name> and self-signed by the principal
specified by the environment (VEYRON_CREDENTIALS) that this tool is
running in. Optionally, the blessing can be restricted with an expiry
caveat specified using the --for flag.
`,
ArgsName: "[<name>]",
ArgsLong: `
<name> is the name used to create the self-signed blessing. If not
specified, a name will be generated based on the hostname of the
machine and the name of the user running this command.
`,
Run: func(cmd *cmdline.Command, args []string) error {
var name string
switch len(args) {
case 0:
name = defaultBlessingName()
case 1:
name = args[0]
default:
return fmt.Errorf("requires at most one argument, provided %d", len(args))
}
var caveats []security.Caveat
if flagBlessSelfFor != 0 {
cav, err := security.ExpiryCaveat(time.Now().Add(flagBlessSelfFor))
if err != nil {
return fmt.Errorf("failed to create expiration Caveat: %v", err)
}
caveats = append(caveats, cav)
}
p, err := principal()
if err != nil {
return err
}
blessing, err := p.BlessSelf(name, caveats...)
if err != nil {
return fmt.Errorf("failed to create self-signed blessing for name %q: %v", name, err)
}
return dumpBlessings(blessing)
},
}
cmdBless = &cmdline.Command{
Name: "bless",
Short: "Bless another principal",
Long: `
Returns a set of blessings obtained when one principal blesses another.
The blesser is obtained from the VEYRON_CREDENTIALS environment variable.
The principal to be blessed is specified as either a path to the VEYRON_CREDENTIALS directory of the other principal, or the filename (or - for STDIN) of any other blessing of that principal.
The blessing that the blesser uses (i.e., which is extended to create the blessing) is the default one from the blessers store, or specified via the --with flag.
The blessing is valid only for the duration specified in --for.
For example, let's say a principal with the default blessing "alice" wants to bless another principal as "alice/bob", the invocation would be:
VEYRON_CREDENTIALS=<path to alice> principal bless <path to bob> friend
`,
ArgsName: "<principal to bless> <extension>",
ArgsLong: `
<principal to bless> represents the principal to be blessed (i.e., whose public key will be provided with a name).
This can either be a path to a file containing any other set of blessings of that principal (or - for STDIN) or the
path to the VEYRON_CREDENTIALS directory of that principal.
<extension> is the string extension that will be applied to create the blessing.
`,
Run: func(cmd *cmdline.Command, args []string) error {
if len(args) != 2 {
return fmt.Errorf("require exactly two arguments, provided %d", len(args))
}
p, err := principal()
if err != nil {
return err
}
var with security.Blessings
if len(flagBlessWith) > 0 {
if with, err = decodeBlessings(flagBlessWith); err != nil {
return fmt.Errorf("failed to read blessings from --with=%q: %v", flagBlessWith, err)
}
} else {
with = p.BlessingStore().Default()
}
var key security.PublicKey
tobless, extension := args[0], args[1]
if finfo, err := os.Stat(tobless); err == nil && finfo.IsDir() {
// TODO(suharshs,ashankar,ataly): How should we make an ecrypted pk... or is that up to the agent?
other, err := vsecurity.LoadPersistentPrincipal(tobless, nil)
if err != nil {
if other, err = vsecurity.CreatePersistentPrincipal(tobless, nil); err != nil {
return fmt.Errorf("failed to read principal in directory %q: %v", tobless, err)
}
}
key = other.PublicKey()
} else if other, err := decodeBlessings(tobless); err != nil {
return fmt.Errorf("failed to decode blessings in %q: %v", tobless, err)
} else {
key = other.PublicKey()
}
caveat, err := security.ExpiryCaveat(time.Now().Add(flagBlessFor))
if err != nil {
return fmt.Errorf("failed to create ExpirtyCaveat: %v", err)
}
blessings, err := p.Bless(key, with, extension, caveat)
if err != nil {
return fmt.Errorf("Bless(%v, %v, %q, ExpiryCaveat(%v)) failed: %v", key, with, extension, flagBlessFor, err)
}
return dumpBlessings(blessings)
},
}
cmdStoreForPeer = &cmdline.Command{
Name: "forpeer",
Short: "Return blessings marked for the provided peer",
Long: `
Returns blessings that are marked for the provided peer in the
BlessingStore specified by the environment (VEYRON_CREDENTIALS)
that this tool is running in.
`,
ArgsName: "[<peer_1> ... <peer_k>]",
ArgsLong: `
<peer_1> ... <peer_k> are the (human-readable string) blessings bound
to the peer. The returned blessings are marked with a pattern that is
matched by at least one of these. If no arguments are specified,
store.forpeer returns the blessings that are marked for all peers (i.e.,
blessings set on the store with the "..." pattern).
`,
Run: func(cmd *cmdline.Command, args []string) error {
p, err := principal()
if err != nil {
return err
}
return dumpBlessings(p.BlessingStore().ForPeer(args...))
},
}
cmdStoreDefault = &cmdline.Command{
Name: "default",
Short: "Return blessings marked as default",
Long: `
Returns blessings that are marked as default in the BlessingStore
specified by the environment (VEYRON_CREDENTIALS) that this tool
is running in.
`,
Run: func(cmd *cmdline.Command, args []string) error {
p, err := principal()
if err != nil {
return err
}
return dumpBlessings(p.BlessingStore().Default())
},
}
cmdStoreSet = &cmdline.Command{
Name: "set",
Short: "Set provided blessings for peer",
Long: `
Marks the provided blessings to be shared with the provided
peers on the BlessingStore specified by the environment
(VEYRON_CREDENTIALS) that this tool is running in.
'set b pattern' marks the intention to reveal b to peers who
present blessings of their own matching 'pattern'.
'set nil pattern' can be used to remove the blessings previously
associated with the pattern (by a prior 'set' command).
It is an error to call 'store.set' with blessings whose public
key does not match the public key of this principal specified
by the environment.
`,
ArgsName: "<file> <pattern>",
ArgsLong: `
<file> is the path to a file containing a blessing typically obtained
from this tool. - is used for STDIN.
<pattern> is the BlessingPattern used to identify peers with whom this
blessing can be shared with.
`,
Run: func(cmd *cmdline.Command, args []string) error {
if len(args) != 2 {
return fmt.Errorf("requires exactly two arguments <file>, <pattern>, provided %d", len(args))
}
blessings, err := decodeBlessings(args[0])
if err != nil {
return fmt.Errorf("failed to decode provided blessings: %v", err)
}
pattern := security.BlessingPattern(args[1])
p, err := principal()
if err != nil {
return err
}
if _, err := p.BlessingStore().Set(blessings, pattern); err != nil {
return fmt.Errorf("failed to set blessings %v for peers %v: %v", blessings, pattern, err)
}
if flagAddToRoots {
if err := p.AddToRoots(blessings); err != nil {
return fmt.Errorf("AddToRoots failed: %v", err)
}
}
return nil
},
}
cmdStoreSetDefault = &cmdline.Command{
Name: "setdefault",
Short: "Set provided blessings as default",
Long: `
Sets the provided blessings as default in the BlessingStore specified
by the environment (VEYRON_CREDENTIALS) that this tool is running in.
It is an error to call 'store.setdefault' with blessings whose public key
does not match the public key of the principal specified by the environment.
`,
ArgsName: "<file>",
ArgsLong: `
<file> is the path to a file containing a blessing typically obtained from
this tool. - is used for STDIN.
`,
Run: func(cmd *cmdline.Command, args []string) error {
if len(args) != 1 {
return fmt.Errorf("requires exactly one argument, <file>, provided %d", len(args))
}
blessings, err := decodeBlessings(args[0])
if err != nil {
return fmt.Errorf("failed to decode provided blessings: %v", err)
}
p, err := principal()
if err != nil {
return err
}
if err = p.BlessingStore().SetDefault(blessings); err != nil {
return fmt.Errorf("failed to set blessings %v as default: %v", blessings, err)
}
if flagAddToRoots {
if err := p.AddToRoots(blessings); err != nil {
return fmt.Errorf("AddToRoots failed: %v", err)
}
}
return nil
},
}
cmdCreate = &cmdline.Command{
Name: "create",
Short: "Create a new principal and persist it into a directory",
Long: `
Creates a new principal with a single self-blessed blessing and writes it out
to the provided directory. The same directory can be used to set the VEYRON_CREDENTIALS
environment variables for other veyron applications.
The operation fails if the directory already contains a principal. In this case
the --overwrite flag can be provided to overwrite the existing principal data in
the directory.
`,
ArgsName: "<directory> <blessing>",
ArgsLong: `
<directory> is the directory to which the principal will be persisted.
<blessing> is the self-blessed blessing that the principal will be setup to use by default.
`,
Run: func(cmd *cmdline.Command, args []string) error {
if len(args) != 2 {
return fmt.Errorf("requires exactly two arguments: <directory> and <blessing>, provided %d", len(args))
}
dir, name := args[0], args[1]
// TODO(suharshs,ashankar,ataly): How should we make an ecrypted pk... or is that up to the agent?
var (
p security.Principal
err error
)
if flagCreateOverwrite {
p, err = vsecurity.CreateOrOverwritePersistentPrincipal(dir, nil)
} else {
p, err = vsecurity.CreatePersistentPrincipal(dir, nil)
}
if err != nil {
return err
}
blessings, err := p.BlessSelf(name)
if err != nil {
return fmt.Errorf("BlessSelf(%q) failed: %v", name, err)
}
if err := p.BlessingStore().SetDefault(blessings); err != nil {
return fmt.Errorf("BlessingStore.SetDefault(%v) failed: %v", blessings, err)
}
if _, err := p.BlessingStore().Set(blessings, security.AllPrincipals); err != nil {
return fmt.Errorf("BlessingStore.Set(%v, %q) failed: %v", blessings, security.AllPrincipals, err)
}
if err := p.AddToRoots(blessings); err != nil {
return fmt.Errorf("AddToRoots(%v) failed: %v", blessings, err)
}
fmt.Printf("%s=%q\n", VEYRON_CREDENTIALS, dir)
return nil
},
}
cmdSeekBlessings = &cmdline.Command{
Name: "seekblessings",
Short: "Seek blessings from a web-based Veyron blesser",
Long: `
Seeks blessings from a web-based Veyron blesser which
requires the caller to first authenticate with Google using OAuth. Simply
run the command to see what happens.
The blessings are sought for the principal specified by the environment
(VEYRON_CREDENTIALS) that this tool is running in.
The blessings obtained are set as default, unless a --skip_set_default flag
is provided, and are also set for sharing with all peers, unless a more
specific peer pattern is provided using the --for_peer flag.
`,
Run: func(cmd *cmdline.Command, args []string) error {
// Initialize the runtime first so that any local errors are reported
// before the HTTP roundtrips for obtaining the macaroon begin.
r, err := runtime()
if err != nil {
return err
}
blessedChan := make(chan string)
defer close(blessedChan)
macaroonChan, err := getMacaroonForBlessRPC(flagSeekBlessingsFrom, blessedChan)
if err != nil {
return fmt.Errorf("failed to get macaroon from Veyron blesser: %v", err)
}
macaroon := <-macaroonChan
service := <-macaroonChan
ctx, cancel := r.NewContext().WithTimeout(time.Minute)
defer cancel()
var reply vdlutil.Any
blesser, err := identity.BindMacaroonBlesser(service)
if err == nil {
reply, err = blesser.Bless(ctx, macaroon)
}
if err != nil {
return fmt.Errorf("failed to get blessing from %q: %v", service, err)
}
wire, ok := reply.(security.WireBlessings)
if !ok {
return fmt.Errorf("received %T, want security.WireBlessings", reply)
}
blessings, err := security.NewBlessings(wire)
if err != nil {
return fmt.Errorf("failed to construct Blessings object from wire data: %v", err)
}
blessedChan <- fmt.Sprint(blessings)
// Wait for getTokenForBlessRPC to clean up:
<-macaroonChan
if flagSeekBlessingsSetDefault {
if err := r.Principal().BlessingStore().SetDefault(blessings); err != nil {
return fmt.Errorf("failed to set blessings %v as default: %v", blessings, err)
}
}
if pattern := security.BlessingPattern(flagSeekBlessingsForPeer); len(pattern) > 0 {
if _, err := r.Principal().BlessingStore().Set(blessings, pattern); err != nil {
return fmt.Errorf("failed to set blessings %v for peers %v: %v", blessings, pattern, err)
}
}
if flagAddToRoots {
if err := r.Principal().AddToRoots(blessings); err != nil {
return fmt.Errorf("AddToRoots failed: %v", err)
}
}
return dumpBlessings(blessings)
},
}
)
func main() {
cmdBlessSelf.Flags.DurationVar(&flagBlessSelfFor, "for", 0, "Duration of blessing validity (zero means no that the blessing is always valid)")
cmdBless.Flags.DurationVar(&flagBlessFor, "for", time.Minute, "Duration of blessing validity")
cmdBless.Flags.StringVar(&flagBlessWith, "with", "", "Path to file containing blessing to extend. ")
cmdSeekBlessings.Flags.StringVar(&flagSeekBlessingsFrom, "from", "https://proxy.envyor.com:8125/google", "URL to use to begin the seek blessings process")
cmdSeekBlessings.Flags.BoolVar(&flagSeekBlessingsSetDefault, "set_default", true, "If true, the blessings obtained will be set as the default blessing in the store")
cmdSeekBlessings.Flags.StringVar(&flagSeekBlessingsForPeer, "for_peer", string(security.AllPrincipals), "If non-empty, the blessings obtained will be marked for peers matching this pattern in the store")
cmdSeekBlessings.Flags.BoolVar(&flagAddToRoots, "add_to_roots", true, "If true, the root certificate of the blessing will be added to the principal's set of recognized root certificates")
cmdStoreSet.Flags.BoolVar(&flagAddToRoots, "add_to_roots", true, "If true, the root certificate of the blessing will be added to the principal's set of recognized root certificates")
cmdStoreSetDefault.Flags.BoolVar(&flagAddToRoots, "add_to_roots", true, "If true, the root certificate of the blessing will be added to the principal's set of recognized root certificates")
cmdCreate.Flags.BoolVar(&flagCreateOverwrite, "overwrite", false, "If true, any existing principal data in the directory will be overwritten")
cmdStore := &cmdline.Command{
Name: "store",
Short: "Manipulate and inspect the principal's blessing store",
Long: `
Commands to manipulate and inspect the blessing store of the principal.
All blessings are printed to stdout using base64-VOM-encoding
`,
Children: []*cmdline.Command{cmdStoreDefault, cmdStoreSetDefault, cmdStoreForPeer, cmdStoreSet},
}
(&cmdline.Command{
Name: "principal",
Short: "Create and manage veyron principals",
Long: `
The principal tool helps create and manage blessings and the set of trusted
roots bound to a principal.
All objects are printed using base64-VOM-encoding.
`,
Children: []*cmdline.Command{cmdCreate, cmdSeekBlessings, cmdDump, cmdDumpBlessings, cmdBlessSelf, cmdBless, cmdStore},
}).Main()
}
func runtime() (veyron2.Runtime, error) {
if len(os.Getenv(VEYRON_CREDENTIALS)) == 0 {
return nil, fmt.Errorf("VEYRON_CREDENTIALS environment variable must be set")
}
return rt.Init(), nil
}
func principal() (security.Principal, error) {
r, err := runtime()
if err != nil {
return nil, err
}
return r.Principal(), nil
}
func decodeBlessings(fname string) (security.Blessings, error) {
var wire security.WireBlessings
if err := decode(fname, &wire); err != nil {
return nil, err
}
return security.NewBlessings(wire)
}
func dumpBlessings(blessings security.Blessings) error {
if blessings == nil {
return errors.New("no blessings found")
}
str, err := util.Base64VomEncode(blessings)
if err != nil {
return fmt.Errorf("base64-VOM encoding failed: %v", err)
}
fmt.Println(str)
return nil
}
func read(fname string) (string, error) {
if len(fname) == 0 {
return "", nil
}
f := os.Stdin
if fname != "-" {
var err error
if f, err = os.Open(fname); err != nil {
return "", fmt.Errorf("failed to open %q: %v", fname, err)
}
}
defer f.Close()
var buf bytes.Buffer
if _, err := io.Copy(&buf, f); err != nil {
return "", fmt.Errorf("failed to read %q: %v", fname, err)
}
return buf.String(), nil
}
func decode(fname string, val interface{}) error {
str, err := read(fname)
if err != nil {
return err
}
if err := util.Base64VomDecode(str, val); err != nil || val == nil {
return fmt.Errorf("failed to decode %q: %v", fname, err)
}
return nil
}
func defaultBlessingName() string {
var name string
if user, _ := user.Current(); user != nil && len(user.Username) > 0 {
name = user.Username
} else {
name = "anonymous"
}
if host, _ := os.Hostname(); len(host) > 0 {
name = name + "@" + host
}
return name
}
func rootkey(chain []security.Certificate) string {
if len(chain) == 0 {
return "<empty certificate chain>"
}
key, err := security.UnmarshalPublicKey(chain[0].PublicKey)
if err != nil {
return fmt.Sprintf("<invalid PublicKey: %v>", err)
}
return fmt.Sprintf("%v", key)
}