Add pass cred storage support (#731)

* Add `pass` cred storage support

Make it the default on non-Windows/macOS

Document the token storage behavior
This commit is contained in:
Jeff Mitchell authored and GitHub committed 2020-10-21 09:00:17 -04:00
1 parent bfa3edbbac
commit 09b865ab28
8 files changed
+326 -94

No files matched your search

+13 -2
View File
@@ -4,10 +4,21 @@ Canonical reference for changes, improvements, and bugfixes for Boundary.
## vNext
### What's New
### Changes/Deprecations
### Improvements
* cli: There are two changes to token storage handling:
* Specifying `none` for the `token-name` parameter has been deprecated in
favor of specifying `none` for the new `keyring-type` parameter.
* [`pass`](https://www.passwordstore.org/) is now the default keyring type on
non-Windows/non-macOS platforms. See the [CLI docs
page](https://www.boundaryproject.io/docs/api-clients/cli) for more
information.
### New and Improved
* cli: New `keyring-type` option and `pass` keyring type for token storage
([Issue](https://github.com/hashicorp/boundary/issues/697))
([PR](https://github.com/hashicorp/boundary/issues/731))
* controller: Allow API/Cluster listeners to be Unix domain sockets
([Issue](https://github.com/hashicorp/boundary/pull/699))
([PR](https://github.com/hashicorp/boundary/pull/705))
+6 -5
View File
@@ -48,14 +48,15 @@ func (d *OutputStringError) parseRequest() {
for k, v := range d.Request.Header {
for _, h := range v {
if strings.ToLower(k) == "authorization" {
env := os.Getenv("BOUNDARY_TOKEN_NAME")
switch env {
case "none":
tokenName := os.Getenv("BOUNDARY_TOKEN_NAME")
keyringType := os.Getenv("BOUNDARY_KEYRING_TYPE")
switch {
case tokenName == "none" || keyringType == "none":
h = `Bearer <token>`
case "":
case tokenName == "" && keyringType == "":
h = `Bearer $(boundary config get-token)`
default:
h = fmt.Sprintf("Bearer $(boundary config get-token -token-name %s)", env)
h = fmt.Sprintf("Bearer $(boundary config get-token -keyring-type %s -token-name %s)", keyringType, tokenName)
}
}
d.parsedCurlString = fmt.Sprintf("%s-H \"%s: %s\" ", d.parsedCurlString, k, h)
+1
View File
@@ -7,6 +7,7 @@ replace github.com/hashicorp/boundary/api => ./api
replace github.com/hashicorp/boundary/sdk => ./sdk
require (
github.com/99designs/keyring v1.1.6
github.com/armon/go-metrics v0.3.4
github.com/bufbuild/buf v0.24.0
github.com/fatih/color v1.9.0
+12
View File
@@ -38,6 +38,8 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/99designs/keyring v1.1.6 h1:kVDC2uCgVwecxCk+9zoCt2uEL6dt+dfVzMvGgnVcIuM=
github.com/99designs/keyring v1.1.6/go.mod h1:16e0ds7LGQQcT59QqkTg72Hh5ShM51Byv5PEmW6uoRU=
github.com/AlecAivazis/survey/v2 v2.0.5 h1:xpZp+Q55wi5C7Iaze+40onHnEkex1jSc34CltJjOoPM=
github.com/AlecAivazis/survey/v2 v2.0.5/go.mod h1:WYBhg6f0y/fNYUuesWQc0PKbJcEliGcYHB9sNT3Bg74=
github.com/Azure/azure-sdk-for-go v36.2.0+incompatible h1:09cv2WoH0g6jl6m2iT+R9qcIPZKhXEL0sbmLhxP895s=
@@ -252,6 +254,8 @@ github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dvsekhvalnov/jose2go v0.0.0-20200901110807-248326c1351b h1:HBah4D48ypg3J7Np4N+HY/ZR76fx3HEUGxDU6Uk39oQ=
github.com/dvsekhvalnov/jose2go v0.0.0-20200901110807-248326c1351b/go.mod h1:7BvyPhdbLxMXIYTFPLsyJRFMsKmOZnQmzh6Gb+uquuM=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
@@ -423,6 +427,7 @@ github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
github.com/gocql/gocql v0.0.0-20190301043612-f6df8288f9b4/go.mod h1:4Fw1eo5iaEhDUs8XyuhSVCVy52Jq3L+/3GJgYkwc+/0=
github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4=
github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
@@ -531,6 +536,8 @@ github.com/grpc-ecosystem/grpc-gateway v1.9.5 h1:UImYN5qQ8tuGpGE16ZmjvcTtTw24zw1
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.0.0-beta.5 h1:fhL1nRLxe6ppWSDwbDMhUIQVVJ8rxskTxWMBRJ8RRiI=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.0.0-beta.5/go.mod h1:fR9dOEfEyRM7ltVH0FTpK/QA6L/5BQq8izXNRu/gyVc=
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU=
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0=
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
@@ -725,6 +732,8 @@ github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaR
github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d h1:Z+RDyXzjKE0i2sTjZ/b1uxiGtPhFy34Ou/Tk0qwN0kM=
github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d/go.mod h1:JJNrCn9otv/2QP4D7SMJBgaleKpOf66PnW6F5WGNRIc=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.2.0 h1:reN85Pxc5larApoH1keMBiu2GWtPqXQ1nc9gx+jOU+E=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
@@ -844,6 +853,8 @@ github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs=
github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns=
github.com/mutecomm/go-sqlcipher/v4 v4.4.0/go.mod h1:PyN04SaWalavxRGH9E8ZftG6Ju7rsPrGmQRjrEaVpiY=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
@@ -1269,6 +1280,7 @@ golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+135 -22
View File
@@ -12,17 +12,19 @@ import (
"os"
"os/signal"
"regexp"
"runtime"
"strings"
"sync"
"syscall"
nkeyring "github.com/99designs/keyring"
"github.com/hashicorp/boundary/api"
"github.com/hashicorp/boundary/api/authtokens"
"github.com/hashicorp/boundary/sdk/wrapper"
"github.com/mitchellh/cli"
"github.com/pkg/errors"
"github.com/posener/complete"
"github.com/zalando/go-keyring"
zkeyring "github.com/zalando/go-keyring"
)
const (
@@ -31,6 +33,7 @@ const (
envToken = "BOUNDARY_TOKEN"
EnvTokenName = "BOUNDARY_TOKEN_NAME"
EnvKeyringType = "BOUNDARY_KEYRING_TYPE"
envRecoveryConfig = "BOUNDARY_RECOVERY_CONFIG"
)
@@ -61,6 +64,7 @@ type Command struct {
flagFormat string
FlagToken string
FlagTokenName string
FlagKeyringType string
FlagRecoveryConfig string
flagOutputCurlString bool
@@ -175,7 +179,6 @@ func (c *Command) Client(opt ...Option) (*api.Client, error) {
c.client.SetMaxRetries(0)
}
tokenName := "default"
switch {
case c.FlagRecoveryConfig != "":
wrapper, err := wrapper.GetWrapperFromPath(c.FlagRecoveryConfig, "recovery")
@@ -209,15 +212,14 @@ func (c *Command) Client(opt ...Option) (*api.Client, error) {
c.client.SetToken(c.FlagToken)
case c.client.Token() == "":
if c.FlagTokenName != "" {
tokenName = c.FlagTokenName
keyringType, tokenName, err := c.DiscoverKeyringTokenInfo()
if err != nil {
return nil, err
}
os.Setenv(EnvTokenName, tokenName)
if tokenName != "none" {
authToken := c.ReadTokenFromKeyring(tokenName)
if authToken != nil {
c.client.SetToken(authToken.Token)
}
authToken := c.ReadTokenFromKeyring(keyringType, tokenName)
if authToken != nil {
c.client.SetToken(authToken.Token)
}
}
@@ -228,18 +230,121 @@ func (c *Command) Client(opt ...Option) (*api.Client, error) {
return c.client, nil
}
func (c *Command) ReadTokenFromKeyring(tokenName string) *authtokens.AuthToken {
token, err := keyring.Get("HashiCorp Boundary Auth Token", tokenName)
if err != nil {
if err == keyring.ErrNotFound {
c.UI.Info("No saved credential found, continuing without")
} else {
// TODO: potentially look for dbus-launch in advance and don't issue a warning at all
c.UI.Error(fmt.Sprintf("Error reading auth token from system credential store: %s", err))
c.UI.Warn("Token must be provided via BOUNDARY_TOKEN env var or -token flag. Reading the token can also be disabled via -token-name=none.")
}
token = ""
func (c *Command) DiscoverKeyringTokenInfo() (string, string, error) {
tokenName := "default"
if c.FlagTokenName != "" {
tokenName = c.FlagTokenName
}
if tokenName == "none" {
c.UI.Warn(`"-token-name=none" is deprecated, please use "-keyring-type=none"`)
c.FlagKeyringType = "none"
}
if c.FlagKeyringType == "none" {
return "", "", nil
}
// Set so we can look it up later when printing out curl strings
os.Setenv(EnvTokenName, tokenName)
var foundKeyringType bool
keyringType := c.FlagKeyringType
switch runtime.GOOS {
case "windows":
switch keyringType {
case "auto", "wincred", "pass":
foundKeyringType = true
if keyringType == "auto" {
keyringType = "wincred"
}
}
case "darwin":
switch keyringType {
case "auto", "keychain", "pass":
foundKeyringType = true
if keyringType == "auto" {
keyringType = "keychain"
}
}
default:
switch keyringType {
case "auto", "secret-service", "pass":
foundKeyringType = true
if keyringType == "auto" {
keyringType = "pass"
}
}
}
if !foundKeyringType {
return "", "", fmt.Errorf("Given keyring type %q is not valid, or not valid for this platform", c.FlagKeyringType)
}
var available bool
switch keyringType {
case "wincred", "keychain":
available = true
case "pass", "secret-service":
avail := nkeyring.AvailableBackends()
for _, a := range avail {
if keyringType == string(a) {
available = true
}
}
}
if !available {
return "", "", fmt.Errorf("Keyring type %q is not available on this machine. For help with setting up keyrings, see https://www.boundaryproject.io/docs/api-clients/cli.", keyringType)
}
os.Setenv(EnvKeyringType, keyringType)
return keyringType, tokenName, nil
}
func (c *Command) ReadTokenFromKeyring(keyringType, tokenName string) *authtokens.AuthToken {
var token string
var err error
switch keyringType {
case "wincred", "keychain":
token, err = zkeyring.Get("HashiCorp Boundary Auth Token", tokenName)
if err != nil {
if err == zkeyring.ErrNotFound {
c.UI.Info("No saved credential found, continuing without")
} else {
c.UI.Error(fmt.Sprintf("Error reading auth token from keyring: %s", err))
c.UI.Warn("Token must be provided via BOUNDARY_TOKEN env var or -token flag. Reading the token can also be disabled via -keyring-type=none.")
}
token = ""
}
default:
krConfig := nkeyring.Config{
LibSecretCollectionName: "login",
PassPrefix: "HashiCorp_Boundary",
AllowedBackends: []nkeyring.BackendType{nkeyring.BackendType(keyringType)},
}
kr, err := nkeyring.Open(krConfig)
if err != nil {
c.UI.Error(fmt.Sprintf("Error opening keyring: %s", err))
c.UI.Warn("Token must be provided via BOUNDARY_TOKEN env var or -token flag. Reading the token can also be disabled via -keyring-type=none.")
break
}
item, err := kr.Get(tokenName)
if err != nil {
c.UI.Error(fmt.Sprintf("Error fetching token from keyring: %s", err))
c.UI.Warn("Token must be provided via BOUNDARY_TOKEN env var or -token flag. Reading the token can also be disabled via -keyring-type=none.")
break
}
token = string(item.Data)
}
if token != "" {
tokenBytes, err := base64.RawStdEncoding.DecodeString(token)
switch {
@@ -349,7 +454,15 @@ func (c *Command) FlagSet(bit FlagSetBit) *FlagSets {
Name: "token-name",
Target: &c.FlagTokenName,
EnvVar: EnvTokenName,
Usage: `If specified, the given value will be used as the name when storing the token in the system credential store. This can allow switching user identities for different commands. Set to "none" to disable storing the token.`,
Usage: `If specified, the given value will be used as the name when storing the token in the system credential store. This can allow switching user identities for different commands.`,
})
f.StringVar(&StringVar{
Name: "keyring-type",
Target: &c.FlagKeyringType,
Default: "auto",
EnvVar: EnvKeyringType,
Usage: `The type of keyring to use. Defaults to "auto" which will use the Windows credential manager, OSX keychain, or cross-platform password store depending on platform. Set to "none" to disable keyring functionality. Available types, depending on platform, are: "wincred", "keychain", "pass", and "secret-service".`,
})
f.StringVar(&StringVar{
+48 -13
View File
@@ -8,6 +8,7 @@ import (
"strings"
"time"
nkeyring "github.com/99designs/keyring"
"github.com/hashicorp/boundary/api"
"github.com/hashicorp/boundary/api/authmethods"
"github.com/hashicorp/boundary/api/authtokens"
@@ -16,7 +17,7 @@ import (
"github.com/mitchellh/cli"
"github.com/mitchellh/go-wordwrap"
"github.com/posener/complete"
"github.com/zalando/go-keyring"
zkeyring "github.com/zalando/go-keyring"
)
var _ cli.Command = (*PasswordCommand)(nil)
@@ -158,22 +159,56 @@ func (c *PasswordCommand) Run(args []string) int {
c.UI.Output(string(jsonOut))
}
tokenName := "default"
if c.Command.FlagTokenName != "" {
tokenName = c.Command.FlagTokenName
}
if tokenName != "none" {
var gotErr bool
keyringType, tokenName, err := c.DiscoverKeyringTokenInfo()
if err != nil {
c.UI.Error(fmt.Sprintf("Error fetching keyring information: %s", err))
gotErr = true
} else if keyringType != "none" &&
tokenName != "none" &&
keyringType != "" &&
tokenName != "" {
marshaled, err := json.Marshal(token)
if err != nil {
c.UI.Error(fmt.Sprintf("Error marshaling auth token to save to system credential store: %s", err))
return 1
}
// TODO: potentially look for dbus-launch in advance and don't issue a warning at all
if err := keyring.Set("HashiCorp Boundary Auth Token", tokenName, base64.RawStdEncoding.EncodeToString(marshaled)); err != nil {
c.UI.Error(fmt.Sprintf("Error saving auth token to system credential store: %s", err))
c.UI.Warn("The token printed above must be manually passed in via the BOUNDARY_TOKEN env var or -token flag. Storing the token can also be disabled via -token-name=none.")
c.UI.Error(fmt.Sprintf("Error marshaling auth token to save to keyring: %s", err))
gotErr = true
} else {
switch keyringType {
case "wincred", "keychain":
if err := zkeyring.Set("HashiCorp Boundary Auth Token", tokenName, base64.RawStdEncoding.EncodeToString(marshaled)); err != nil {
c.UI.Error(fmt.Sprintf("Error saving auth token to %q keyring: %s", keyringType, err))
gotErr = true
}
default:
krConfig := nkeyring.Config{
LibSecretCollectionName: "login",
PassPrefix: "HashiCorp_Boundary",
AllowedBackends: []nkeyring.BackendType{nkeyring.BackendType(keyringType)},
}
kr, err := nkeyring.Open(krConfig)
if err != nil {
c.UI.Error(fmt.Sprintf("Error opening %q keyring: %s", keyringType, err))
gotErr = true
break
}
if err := kr.Set(nkeyring.Item{
Key: tokenName,
Data: []byte(base64.RawStdEncoding.EncodeToString(marshaled)),
}); err != nil {
c.UI.Error(fmt.Sprintf("Error storing token in %q keyring: %s", keyringType, err))
gotErr = true
break
}
}
}
}
if gotErr {
c.UI.Warn("The token printed above must be manually passed in via the BOUNDARY_TOKEN env var or -token flag. Storing the token can also be disabled via -keyring-type=none.")
}
return 0
}
+24 -12
View File
@@ -61,6 +61,14 @@ func (c *TokenCommand) Flags() *base.FlagSets {
Usage: `If specified, the given value will be used as the name when loading the token from the system credential store. This must correspond to a name used when authenticating.`,
})
f.StringVar(&base.StringVar{
Name: "keyring-type",
Target: &c.FlagKeyringType,
Default: "auto",
EnvVar: base.EnvKeyringType,
Usage: `The type of keyring to use. Defaults to "auto" which will use the Windows credential manager, OSX keychain, or cross-platform password store depending on platform. Set to "none" to disable keyring functionality. Available types, depending on platform, are: "wincred", "keychain", "pass", and "secret-service".`,
})
f.BoolVar(&base.BoolVar{
Name: "user-id",
Target: &c.flagUserId,
@@ -113,27 +121,31 @@ func (c *TokenCommand) Run(args []string) (ret int) {
return 1
}
// Read from keyring first
// Read from client first as that will override keyring anyways
var authToken *authtokens.AuthToken
tokenName := "default"
if c.FlagTokenName != "" {
tokenName = c.FlagTokenName
// Fallback to env/CLI
client, err := c.Client()
if err != nil {
c.UI.Error(err.Error())
return 1
}
if tokenName != "none" {
at := c.ReadTokenFromKeyring(tokenName)
if at != nil {
authToken = at
}
if client.Token() != "" {
authToken = &authtokens.AuthToken{Token: client.Token()}
}
if authToken == nil {
// Fallback to env/CLI
client, err := c.Client()
keyringType, tokenName, err := c.DiscoverKeyringTokenInfo()
if err != nil {
c.UI.Error(err.Error())
return 1
}
authToken = &authtokens.AuthToken{Token: client.Token()}
authToken = c.ReadTokenFromKeyring(keyringType, tokenName)
}
if authToken == nil {
c.UI.Error("No token could be discovered")
return 1
}
switch {
+87 -40
View File
@@ -27,6 +27,93 @@ If you want to install it manually, for Bash, the following line in a
`complete -C /path/to/boundary boundary`
## Keyring Token storage
Boundary uses various mechanisms, depending on platform, to allow for secure
storage of authentication tokens for later use. Each platform has a
platform-specific option (which on Windows and macOS are the default);
[pass](https://www.passwordstore.org/) is also available on all platforms. On
all platforms, setting `-keyring-type` to `none` (or setting it via
`BOUNDARY_KEYRING_TYPE`) disables storage and retrieval of the token.
Additionally, more than one token can be stored or retrieved at once via the
`-token-name` flag or `BOUNDARY_TOKEN_NAME` env var. This allows for storing
tokens used by different Boundary installations, or other needs.
### Windows
On Windows, the Windows credential store (`wincred`) is used.
Available keyring types:
* `wincred` (default)
* `pass`
* `none`
### macOS
On macOS, Keychain is used via `/usr/bin/security`. (Using this binary allows us
to keep the Boundary binary statically linked, which we prefer.)
Available keyring types:
* `keychain` (default)
* `pass`
* `none`
### Other platforms
On all other platforms, the default is `pass`. However, if an implementation of
the [freedesktop.org secret
service](https://specifications.freedesktop.org/secret-service/latest/) is
available (via `gnome-keyring`, `kwallet`, or others) it can be used.
Available keyring types:
* `pass` (default)
* `secret-service`
* `none`
## Mapping to Collections and Sub-Types
Generally speaking, Boundary's CLI commands map to the collections they operate
on. For instance, when operating on roles, the command will be `boundary roles
...`.
As a result, the patterns for reading, deleting, and listing are predictable:
* `boundary <collection> read`
* `boundary <collection> delete`
* `boundary <collection> list`
`read` and `delete` will always operate on a particular resource identifier, so
will always take in an `-id` parameter. `list` operates on collections so will
either take a `-scope-id` parameter or, depending on type, a higher level
resource identifier, e.g. `-auth-method-id`.
Creating and updating resources may take an extra parameter if the resource type
is abstract, that is, if the type cannot be operated on directly but must be
operated on through an implementation. As an example, a role is not an abstract
type, and does not have various implementations of it. As a result, a role can
be operated on directly:
* `boundary roles create`
* `boundary roles update`.
However, a target can be one of many types of targets, and a concrete
implementation of a target is a `tcp` type of target. Therefore an extra
parameter is required when creating or updating a target:
* `boundary targets tcp create`
* `boundary targets tcp update`
This allows the CLI to perform proper presentation and validation of parameters
and functions for the given type.
Similar to `read`, `update` operates on an existing target so will always take
an `-id` parameter. Similar to `list`, `create` operates on a collection so will
either take a `-scope-id` parameter or a parameter defining the parent resource.
## Parameter Handling
All parameters specified on the CLI are specified as a Go-style flag with a
@@ -99,43 +186,3 @@ the exact output from the controller. If using the output of the CLI in scripts
or as parameters to other tools, _always_ use formatted output. The default text
output is meant for human users and the formatting or the information included
within that output from the original JSON may change at any time.
## Mapping to Collections and Sub-Types
Generally speaking, Boundary's CLI commands map to the collections they operate
on. For instance, when operating on roles, the command will be `boundary roles
...`.
As a result, the patterns for reading, deleting, and listing are predictable:
* `boundary <collection> read`
* `boundary <collection> delete`
* `boundary <collection> list`
`read` and `delete` will always operate on a particular resource identifier, so
will always take in an `-id` parameter. `list` operates on collections so will
either take a `-scope-id` parameter or, depending on type, a higher level
resource identifier, e.g. `-auth-method-id`.
Creating and updating resources may take an extra parameter if the resource type
is abstract, that is, if the type cannot be operated on directly but must be
operated on through an implementation. As an example, a role is not an abstract
type, and does not have various implementations of it. As a result, a role can
be operated on directly:
* `boundary roles create`
* `boundary roles update`.
However, a target can be one of many types of targets, and a concrete
implementation of a target is a `tcp` type of target. Therefore an extra
parameter is required when creating or updating a target:
* `boundary targets tcp create`
* `boundary targets tcp update`
This allows the CLI to perform proper presentation and validation of parameters
and functions for the given type.
Similar to `read`, `update` operates on an existing target so will always take
an `-id` parameter. Similar to `list`, `create` operates on a collection so will
either take a `-scope-id` parameter or a parameter defining the parent resource.