cmd/principal: Add --file flag to recvblessings and bless.
As per https://github.com/veyron/release-issues/issues/946,
users can now run
principal recvblessings --file foo
The remote_key, remote_token, and principal will be writte to that file,
Then users can invoke
principal bless --file foo extension
to successfully get a blessing.
Change-Id: Icc47c8550f4e5c61e8ff646bf176075e6f453768
diff --git a/cmd/principal/doc.go b/cmd/principal/doc.go
index a461daf..8bdec5f 100644
--- a/cmd/principal/doc.go
+++ b/cmd/principal/doc.go
@@ -184,7 +184,7 @@
principal --veyron.proxy=proxy recvblessings
The command to be run at the sender is of the form:
- principal bless --remote_key=KEY --remote_token=TOKEN ADDRESS
+ principal bless --remote_key=KEY --remote_token=TOKEN ADDRESS EXTENSION
The --remote_key flag is used to by the sender to "authenticate" the receiver,
ensuring it blesses the intended recipient and not any attacker that may have
@@ -194,6 +194,11 @@
receiver. This helps ensure that the receiver rejects blessings from senders who
just happened to guess the network address of the 'recvblessings' invocation.
+If the --remote_arg_file flag is provided to recvblessings, the remote key,
+remote token and object address of this principal will be written to the
+specified location. This file can be supplied to bless:
+ principal bless --remote_arg_file FILE EXTENSION
+
Usage:
principal recvblessings [flags]
@@ -201,6 +206,10 @@
-for_peer=...
If non-empty, the blessings received will be marked for peers matching this
pattern in the store
+ -remote_arg_file=
+ If non-empty, the remote key, remote token, and principal will be written to
+ the specified file in a JSON object. This can be provided to 'principal bless
+ --remote_arg_file FILE EXTENSION'.
-set_default=true
If true, the blessings received will be set as the default blessing in the
store
@@ -263,8 +272,11 @@
dumped to STDOUT but sent to the remote end. Use 'principal help recvblessings'
for more details on that.
+When --remote_arg_file is specified, only the blessing extension is required, as
+all other arguments will be extracted from the specified file.
+
Usage:
- principal bless [flags] <principal to bless> <extension>
+ principal bless [flags] [<principal to bless>] <extension>
<principal to bless> represents the principal to be blessed (i.e., whose public
key will be provided with a name). This can be either: (a) The directory
@@ -274,6 +286,9 @@
OR (c) The object name produced by the 'recvblessings' command of this tool
running on behalf of another principal (if the --remote_key and
--remote_token flags are specified).
+OR (d) None (if the --remote_arg_file flag is specified, only <extension> should
+be provided
+ to bless).
<extension> is the string extension that will be applied to create the blessing.
@@ -282,6 +297,10 @@
"package/path".CaveatName:VDLExpressionParam to attach to this blessing
-for=0
Duration of blessing validity (zero implies no expiration caveat)
+ -remote_arg_file=
+ File containing bless arguments written by 'principal recvblessings
+ -remote_arg_file FILE EXTENSION' command. This can be provided to bless in
+ place of --remote_key, --remote_token, and <principal>.
-remote_key=
Public key of the remote principal to bless (obtained from the
'recvblessings' command run by the remote principal
diff --git a/cmd/principal/main.go b/cmd/principal/main.go
index 8700c37..05c6e2b 100644
--- a/cmd/principal/main.go
+++ b/cmd/principal/main.go
@@ -12,8 +12,10 @@
"crypto/rand"
"crypto/subtle"
"encoding/base64"
+ "encoding/json"
"fmt"
"io"
+ "io/ioutil"
"os"
"os/user"
"time"
@@ -57,6 +59,7 @@
// Flags common to many commands
flagAddToRoots bool
flagCreateOverwrite bool
+ flagRemoteArgFile string
// Flags for the "recvblessings" command
flagRecvBlessingsSetDefault bool
@@ -193,8 +196,11 @@
bless a principal on a remote machine as well. In this case, the blessing is
not dumped to STDOUT but sent to the remote end. Use 'principal help
recvblessings' for more details on that.
+
+When --remote_arg_file is specified, only the blessing extension is required, as all other
+arguments will be extracted from the specified file.
`,
- ArgsName: "<principal to bless> <extension>",
+ 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 be either:
@@ -206,13 +212,19 @@
(c) The object name produced by the 'recvblessings' command of this tool
running on behalf of another principal (if the --remote_key and
--remote_token flags are specified).
+OR
+(d) None (if the --remote_arg_file flag is specified, only <extension> should be provided
+ to bless).
<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))
+ if len(flagRemoteArgFile) > 0 && len(args) != 1 {
+ return fmt.Errorf("when --remote_arg_file is provided, only <extension> is expected, provided %d", len(args))
+ } else if len(flagRemoteArgFile) == 0 && len(args) != 2 {
+ return fmt.Errorf("require exactly two arguments when --remote_arg_file is not provided, provided %d", len(args))
}
ctx, shutdown := v23.Init()
@@ -241,33 +253,23 @@
if len(caveats) == 0 {
return errNoCaveats
}
- tobless, extension := args[0], args[1]
- if (len(flagBlessRemoteKey) == 0) != (len(flagBlessRemoteToken) == 0) {
- return fmt.Errorf("either both --remote_key and --remote_token should be set, or neither should")
- }
- if len(flagBlessRemoteKey) > 0 {
- // Send blessings to a "server" started by a "recvblessings" command
- granter := &granter{p, with, extension, caveats, flagBlessRemoteKey}
- return sendBlessings(ctx, tobless, granter, flagBlessRemoteToken)
- }
- // Blessing a principal whose key is available locally.
- var key security.PublicKey
- if finfo, err := os.Stat(tobless); err == nil && finfo.IsDir() {
- 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()
- }
- blessings, err := p.Bless(key, with, extension, caveats[0], caveats[1:]...)
+
+ tobless, extension, remoteKey, remoteToken, err := blessArgs(args)
if err != nil {
- return fmt.Errorf("Bless(%v, %v, %q, ...) failed: %v", key, with, extension, err)
+ return err
+ }
+
+ // Send blessings to a "server" started by a "recvblessings" command, either
+ // with the --remote_arg_file flag, or with --remote_key and --remote_token flags.
+ if len(remoteKey) > 0 {
+ granter := &granter{p, with, extension, caveats, remoteKey}
+ return blessOverNetwork(ctx, tobless, granter, remoteToken)
+ }
+
+ // Blessing a principal whose key is available locally.
+ blessings, err := blessOverFileSystem(p, tobless, with, extension, caveats)
+ if err != nil {
+ return err
}
return dumpBlessings(blessings)
},
@@ -657,7 +659,7 @@
principal --veyron.proxy=proxy recvblessings
The command to be run at the sender is of the form:
- principal bless --remote_key=KEY --remote_token=TOKEN ADDRESS
+ principal bless --remote_key=KEY --remote_token=TOKEN ADDRESS EXTENSION
The --remote_key flag is used to by the sender to "authenticate" the receiver,
ensuring it blesses the intended recipient and not any attacker that may have
@@ -667,6 +669,12 @@
receiver. This helps ensure that the receiver rejects blessings from senders
who just happened to guess the network address of the 'recvblessings'
invocation.
+
+If the --remote_arg_file flag is provided to recvblessings, the remote key, remote token
+and object address of this principal will be written to the specified location.
+This file can be supplied to bless:
+ principal bless --remote_arg_file FILE EXTENSION
+
`,
Run: func(cmd *cmdline.Command, args []string) error {
if len(args) != 0 {
@@ -705,7 +713,15 @@
fmt.Println("You may want to adjust flags affecting the caveats on this blessing, for example using")
fmt.Println("the --for flag, or change the extension to something more meaningful")
fmt.Println()
- fmt.Printf("principal bless --remote_key=%v --remote_token=%v %v %v\n", p.PublicKey(), service.token, eps[0].Name(), extension)
+ if len(flagRemoteArgFile) > 0 {
+ if err := writeRecvBlessingsInfo(flagRemoteArgFile, p.PublicKey().String(), service.token, eps[0].Name()); err != nil {
+ return fmt.Errorf("failed to write recvblessings info to %v: %v", flagRemoteArgFile, err)
+ }
+ fmt.Printf("make %q accessible to the blesser, possibly by copying the file over and then run:\n", flagRemoteArgFile)
+ fmt.Printf("principal bless --remote_arg_file=%v %v", flagRemoteArgFile, extension)
+ } else {
+ fmt.Printf("principal bless --remote_key=%v --remote_token=%v %v %v\n", p.PublicKey(), service.token, eps[0].Name(), extension)
+ }
fmt.Println()
fmt.Println("...waiting for sender..")
return <-service.notify
@@ -713,6 +729,76 @@
}
)
+func blessArgs(args []string) (tobless, extension, remoteKey, remoteToken string, err error) {
+ if len(flagRemoteArgFile) > 0 && (len(flagBlessRemoteKey)+len(flagBlessRemoteToken) > 0) {
+ return "", "", "", "", fmt.Errorf("--remote_key and --remote_token cannot be provided with --remote_arg_file")
+ }
+ if (len(flagBlessRemoteKey) == 0) != (len(flagBlessRemoteToken) == 0) {
+ return "", "", "", "", fmt.Errorf("either both --remote_key and --remote_token should be set, or neither should")
+ }
+
+ if len(flagRemoteArgFile) == 0 {
+ tobless, extension = args[0], args[1]
+ remoteKey = flagBlessRemoteKey
+ remoteToken = flagBlessRemoteToken
+ } else if len(flagRemoteArgFile) > 0 {
+ extension = args[0]
+ remoteKey, remoteToken, tobless, err = blessArgsFromFile(flagRemoteArgFile)
+ }
+ return
+}
+
+func blessOverFileSystem(p security.Principal, tobless string, with security.Blessings, extension string, caveats []security.Caveat) (security.Blessings, error) {
+ var key security.PublicKey
+ if finfo, err := os.Stat(tobless); err == nil && finfo.IsDir() {
+ other, err := vsecurity.LoadPersistentPrincipal(tobless, nil)
+ if err != nil {
+ if other, err = vsecurity.CreatePersistentPrincipal(tobless, nil); err != nil {
+ return security.Blessings{}, fmt.Errorf("failed to read principal in directory %q: %v", tobless, err)
+ }
+ }
+ key = other.PublicKey()
+ } else if other, err := decodeBlessings(tobless); err != nil {
+ return security.Blessings{}, fmt.Errorf("failed to decode blessings in %q: %v", tobless, err)
+ } else {
+ key = other.PublicKey()
+ }
+ return p.Bless(key, with, extension, caveats[0], caveats[1:]...)
+}
+
+type recvBlessingsInfo struct {
+ RemoteKey string `json:remote_key`
+ RemoteToken string `json:remote_token`
+ Name string `json:name`
+}
+
+func writeRecvBlessingsInfo(fname string, remoteKey, remoteToken, name string) error {
+ f, err := os.OpenFile(fname, os.O_WRONLY|os.O_CREATE, 0666)
+ if err != nil {
+ return err
+ }
+ b, err := json.Marshal(recvBlessingsInfo{remoteKey, remoteToken, name})
+ if err != nil {
+ return err
+ }
+ if _, err := f.Write(b); err != nil {
+ return err
+ }
+ return nil
+}
+
+func blessArgsFromFile(fname string) (remoteKey, remoteToken, tobless string, err error) {
+ blessJSON, err := ioutil.ReadFile(fname)
+ if err != nil {
+ return "", "", "", err
+ }
+ var binfo recvBlessingsInfo
+ if err := json.Unmarshal(blessJSON, &binfo); err != nil {
+ return "", "", "", err
+ }
+ return binfo.RemoteKey, binfo.RemoteToken, binfo.Name, err
+}
+
func main() {
cmdBlessSelf.Flags.Var(&flagBlessSelfCaveats, "caveat", flagBlessSelfCaveats.usage())
cmdBlessSelf.Flags.DurationVar(&flagBlessSelfFor, "for", 0, "Duration of blessing validity (zero implies no expiration)")
@@ -729,6 +815,7 @@
cmdBless.Flags.StringVar(&flagBlessWith, "with", "", "Path to file containing blessing to extend")
cmdBless.Flags.StringVar(&flagBlessRemoteKey, "remote_key", "", "Public key of the remote principal to bless (obtained from the 'recvblessings' command run by the remote principal")
cmdBless.Flags.StringVar(&flagBlessRemoteToken, "remote_token", "", "Token provided by principal running the 'recvblessings' command")
+ cmdBless.Flags.StringVar(&flagRemoteArgFile, "remote_arg_file", "", "File containing bless arguments written by 'principal recvblessings -remote_arg_file FILE EXTENSION' command. This can be provided to bless in place of --remote_key, --remote_token, and <principal>.")
cmdSeekBlessings.Flags.StringVar(&flagSeekBlessingsFrom, "from", "https://dev.v.io/auth/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")
@@ -744,6 +831,7 @@
cmdRecvBlessings.Flags.BoolVar(&flagRecvBlessingsSetDefault, "set_default", true, "If true, the blessings received will be set as the default blessing in the store")
cmdRecvBlessings.Flags.StringVar(&flagRecvBlessingsForPeer, "for_peer", string(security.AllPrincipals), "If non-empty, the blessings received will be marked for peers matching this pattern in the store")
+ cmdRecvBlessings.Flags.StringVar(&flagRemoteArgFile, "remote_arg_file", "", "If non-empty, the remote key, remote token, and principal will be written to the specified file in a JSON object. This can be provided to 'principal bless --remote_arg_file FILE EXTENSION'.")
cmdSet := &cmdline.Command{
Name: "set",
@@ -948,7 +1036,7 @@
}
func (*granter) RPCCallOpt() {}
-func sendBlessings(ctx *context.T, object string, granter *granter, remoteToken string) error {
+func blessOverNetwork(ctx *context.T, object string, granter *granter, remoteToken string) error {
client := v23.GetClient(ctx)
// The receiver is being authorized based on the hash of its public key
// (see Grant), so it should be fine to ignore the blessing names in the endpoint
diff --git a/cmd/principal/principal_v23_test.go b/cmd/principal/principal_v23_test.go
index 174e224..0f27924 100644
--- a/cmd/principal/principal_v23_test.go
+++ b/cmd/principal/principal_v23_test.go
@@ -12,7 +12,6 @@
"path/filepath"
"regexp"
"strings"
- "syscall"
"v.io/x/ref/envvar"
"v.io/x/ref/test/v23tests"
@@ -148,14 +147,17 @@
func V23TestRecvBlessings(t *v23tests.T) {
var (
- outputDir = t.NewTempDir()
- bin = t.BuildGoPkg("v.io/x/ref/cmd/principal")
- aliceDir = filepath.Join(outputDir, "alice")
- carolDir = filepath.Join(outputDir, "carol")
+ outputDir = t.NewTempDir()
+ bin = t.BuildGoPkg("v.io/x/ref/cmd/principal")
+ aliceDir = filepath.Join(outputDir, "alice")
+ bobDir = filepath.Join(outputDir, "bob")
+ carolDir = filepath.Join(outputDir, "carol")
+ bobBlessFile = filepath.Join(outputDir, "bobBlessInfo")
)
// Generate principals for alice and carol.
bin.Start("create", aliceDir, "alice").WaitOrDie(os.Stdout, os.Stderr)
+ bin.Start("create", bobDir, "bob").WaitOrDie(os.Stdout, os.Stderr)
bin.Start("create", carolDir, "carol").WaitOrDie(os.Stdout, os.Stderr)
// Run recvblessings on carol, and have alice send blessings over
@@ -163,27 +165,32 @@
var args []string
{
inv := bin.Start("--veyron.credentials="+carolDir, "--veyron.tcp.address=127.0.0.1:0", "recvblessings")
- defer inv.Kill(syscall.SIGTERM)
args = append([]string{"bless", "--require_caveats=false"}, blessArgsFromRecvBlessings(inv)...)
// Replace the random extension suggested by recvblessings with "friend/carol"
args[len(args)-1] = "friend/carol"
}
-
bin.WithEnv(credEnv(aliceDir)).Start(args...).WaitOrDie(os.Stdout, os.Stderr)
// Run recvblessings on carol, and have alice send blessings over
// (blessings received must be set as shareable with peers matching 'alice/...'.)
{
inv := bin.Start("--veyron.credentials="+carolDir, "--veyron.tcp.address=127.0.0.1:0", "recvblessings", "--for_peer=alice", "--set_default=false")
- defer inv.Kill(syscall.SIGTERM)
// recvblessings suggests a random extension, find the extension and replace it with friend/carol/foralice.
args = append([]string{"bless", "--require_caveats=false"}, blessArgsFromRecvBlessings(inv)...)
args[len(args)-1] = "friend/carol/foralice"
}
bin.WithEnv(credEnv(aliceDir)).Start(args...).WaitOrDie(os.Stdout, os.Stderr)
+ // Run recvblessings on carol with the --remote_arg_file flag, and have bob send blessings over with the --remote_arg_file flag.
+ {
+ inv := bin.Start("--veyron.credentials="+carolDir, "--veyron.tcp.address=127.0.0.1:0", "recvblessings", "--for_peer=bob", "--set_default=false", "--remote_arg_file="+bobBlessFile)
+ // recvblessings suggests a random extension, use friend/carol/forbob instead.
+ args = append([]string{"bless", "--require_caveats=false"}, blessArgsFromRecvBlessings(inv)...)
+ args[len(args)-1] = "friend/carol/forbob"
+ }
+ bin.WithEnv(credEnv(bobDir)).Start(args...).WaitOrDie(os.Stdout, os.Stderr)
+
listenerInv := bin.Start("--veyron.credentials="+carolDir, "--veyron.tcp.address=127.0.0.1:0", "recvblessings", "--for_peer=alice/...", "--set_default=false", "--vmodule=*=2", "--logtostderr")
- defer listenerInv.Kill(syscall.SIGTERM)
args = append([]string{"bless", "--require_caveats=false"}, blessArgsFromRecvBlessings(listenerInv)...)
@@ -213,8 +220,7 @@
}
}
- // Dump carol out, the only blessing that survives should be from the
- // first "bless" command. (alice/friend/carol).
+ // Dump carol out, all three blessings should be in the store.
got := removePublicKeys(bin.Start("--veyron.credentials="+carolDir, "dump").Output())
want := `Public key : XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
---------------- BlessingStore ----------------
@@ -222,9 +228,11 @@
Peer pattern : Blessings
... : alice/friend/carol
alice : alice/friend/carol/foralice
+bob : bob/friend/carol/forbob
---------------- BlessingRoots ----------------
Public key : Pattern
XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX : [alice]
+XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX : [bob]
XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX : [carol]
`
if want != got {