Merge branch 'master' into jo-upgrade-copy

This commit is contained in:
Joshua Ogle 2018-05-25 12:59:50 -06:00 committed by GitHub
commit 80262092af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
218 changed files with 9833 additions and 1385 deletions

View File

@ -8,21 +8,27 @@ DEPRECATIONS/CHANGES:
FEATURES:
* Azure Key Vault Auto Unseal/Seal Wrap Support (Enterprise): Azure Key Vault
can now be used a support seal for Auto Unseal and Seal Wrapping.
* Cert auth CIDR restrictions: When using the `cert` auth method you can now
limit authentication to specific CIDRs; these will also be encoded in
resultant tokens to limit their use.
* Userpass auth CIDR restrictions: When using the `userpass` auth method you
can now limit authentication to specific CIDRs; these will also be encoded
in resultant tokens to limit their use.
IMPROVEMENTS:
* api: Close renewer's doneCh when the renewer is stopped, so that programs
expecting a final value through doneCh behave correctly [GH-4472]
* auth/cert: Break out `allowed_names` into component parts and add
`allowed_uri_sans` [GH-4231]
* cli: `vault login` now supports a `-no-print` flag to suppress printing
token information but still allow storing into the token helper [GH-4454]
* core/pkcs11 (enterprise): Add support for CKM_AES_CBS_PAD, CKM_RSA_PKCS, and
* core/pkcs11 (enterprise): Add support for CKM_AES_CBC_PAD, CKM_RSA_PKCS, and
CKM_RSA_PKCS_OAEP mechanisms
* core/pkcs11 (enterprise): HSM slots can now be selected by token label instead
of just slot number
* core/seal (enterprise): Lazily rewrap data when seal keys are rotated
* expiration: Allow revoke-prefix and revoke-force to work on single leases as
well as prefixes [GH-4450]
@ -39,12 +45,19 @@ BUG FIXES:
parameters were used [GH-4582]
* secret/gcp: Make `bound_region` able to use short names
* secret/kv: Fix response wrapping for KV v2 [GH-4511]
* secret/kv: Fix address flag not being honored correctly [GH-4617]
* secret/pki: Fix `key_type` not being allowed to be set to `any` [GH-4595]
* secret/pki: Fix path length parameter being ignored when using
`use_csr_values` and signing an intermediate CA cert [GH-4459]
* storage/dynamodb: Fix listing when one child is left within a nested path
[GH-4570]
* ui: Fix HMAC algorithm in transit [GH-4604]
* ui: Fix unwrap of auth responses via the UI's unwrap tool [GH-4611]
* replication: Fix error while running plugins on a newly created replication
secondary
* replication: Fix issue with token store lookups after a secondary's mount table
is invalidated.
* replication: Improve startup time when a large merkle index is in use.
## 0.10.1/0.9.7 (April 25th, 2018)

View File

@ -388,11 +388,12 @@ func (c *Client) SetAddress(addr string) error {
c.modifyLock.Lock()
defer c.modifyLock.Unlock()
var err error
if c.addr, err = url.Parse(addr); err != nil {
parsedAddr, err := url.Parse(addr)
if err != nil {
return errwrap.Wrapf("failed to set address: {{err}}", err)
}
c.addr = parsedAddr
return nil
}
@ -411,7 +412,8 @@ func (c *Client) SetLimiter(rateLimit float64, burst int) {
c.modifyLock.RLock()
c.config.modifyLock.Lock()
defer c.config.modifyLock.Unlock()
defer c.modifyLock.RUnlock()
c.modifyLock.RUnlock()
c.config.Limiter = rate.NewLimiter(rate.Limit(rateLimit), burst)
}
@ -544,14 +546,20 @@ func (c *Client) SetPolicyOverride(override bool) {
// doesn't need to be called externally.
func (c *Client) NewRequest(method, requestPath string) *Request {
c.modifyLock.RLock()
defer c.modifyLock.RUnlock()
addr := c.addr
token := c.token
mfaCreds := c.mfaCreds
wrappingLookupFunc := c.wrappingLookupFunc
headers := c.headers
policyOverride := c.policyOverride
c.modifyLock.RUnlock()
// if SRV records exist (see https://tools.ietf.org/html/draft-andrews-http-srv-02), lookup the SRV
// record and take the highest match; this is not designed for high-availability, just discovery
var host string = c.addr.Host
if c.addr.Port() == "" {
var host string = addr.Host
if addr.Port() == "" {
// Internet Draft specifies that the SRV record is ignored if a port is given
_, addrs, err := net.LookupSRV("http", "tcp", c.addr.Hostname())
_, addrs, err := net.LookupSRV("http", "tcp", addr.Hostname())
if err == nil && len(addrs) > 0 {
host = fmt.Sprintf("%s:%d", addrs[0].Target, addrs[0].Port)
}
@ -560,12 +568,12 @@ func (c *Client) NewRequest(method, requestPath string) *Request {
req := &Request{
Method: method,
URL: &url.URL{
User: c.addr.User,
Scheme: c.addr.Scheme,
User: addr.User,
Scheme: addr.Scheme,
Host: host,
Path: path.Join(c.addr.Path, requestPath),
Path: path.Join(addr.Path, requestPath),
},
ClientToken: c.token,
ClientToken: token,
Params: make(map[string][]string),
}
@ -579,21 +587,19 @@ func (c *Client) NewRequest(method, requestPath string) *Request {
lookupPath = requestPath
}
req.MFAHeaderVals = c.mfaCreds
req.MFAHeaderVals = mfaCreds
if c.wrappingLookupFunc != nil {
req.WrapTTL = c.wrappingLookupFunc(method, lookupPath)
if wrappingLookupFunc != nil {
req.WrapTTL = wrappingLookupFunc(method, lookupPath)
} else {
req.WrapTTL = DefaultWrappingLookupFunc(method, lookupPath)
}
if c.config.Timeout != 0 {
c.config.HttpClient.Timeout = c.config.Timeout
}
if c.headers != nil {
req.Headers = c.headers
if headers != nil {
req.Headers = headers
}
req.PolicyOverride = c.policyOverride
req.PolicyOverride = policyOverride
return req
}
@ -602,18 +608,23 @@ func (c *Client) NewRequest(method, requestPath string) *Request {
// a Vault server not configured with this client. This is an advanced operation
// that generally won't need to be called externally.
func (c *Client) RawRequest(r *Request) (*Response, error) {
c.modifyLock.RLock()
c.config.modifyLock.RLock()
defer c.config.modifyLock.RUnlock()
if c.config.Limiter != nil {
c.config.Limiter.Wait(context.Background())
}
token := c.token
c.config.modifyLock.RLock()
limiter := c.config.Limiter
maxRetries := c.config.MaxRetries
backoff := c.config.Backoff
httpClient := c.config.HttpClient
timeout := c.config.Timeout
c.config.modifyLock.RUnlock()
c.modifyLock.RUnlock()
if limiter != nil {
limiter.Wait(context.Background())
}
// Sanity check the token before potentially erroring from the API
idx := strings.IndexFunc(token, func(c rune) bool {
return !unicode.IsPrint(c)
@ -632,16 +643,23 @@ START:
return nil, fmt.Errorf("nil request created")
}
backoff := c.config.Backoff
// Set the timeout, if any
var cancelFunc context.CancelFunc
if timeout != 0 {
var ctx context.Context
ctx, cancelFunc = context.WithTimeout(context.Background(), timeout)
req.Request = req.Request.WithContext(ctx)
}
if backoff == nil {
backoff = retryablehttp.LinearJitterBackoff
}
client := &retryablehttp.Client{
HTTPClient: c.config.HttpClient,
HTTPClient: httpClient,
RetryWaitMin: 1000 * time.Millisecond,
RetryWaitMax: 1500 * time.Millisecond,
RetryMax: c.config.MaxRetries,
RetryMax: maxRetries,
CheckRetry: retryablehttp.DefaultRetryPolicy,
Backoff: backoff,
ErrorHandler: retryablehttp.PassthroughErrorHandler,
@ -649,6 +667,9 @@ START:
var result *Response
resp, err := client.Do(req)
if cancelFunc != nil {
cancelFunc()
}
if resp != nil {
result = &Response{Response: resp}
}

View File

@ -7,7 +7,6 @@ import (
"os"
"strings"
"testing"
"time"
)
func init() {
@ -244,22 +243,10 @@ func TestClientTimeoutSetting(t *testing.T) {
defer os.Setenv(EnvVaultClientTimeout, oldClientTimeout)
config := DefaultConfig()
config.ReadEnvironment()
client, err := NewClient(config)
_, err := NewClient(config)
if err != nil {
t.Fatal(err)
}
_ = client.NewRequest("PUT", "/")
if client.config.HttpClient.Timeout != time.Second*10 {
t.Fatalf("error setting client timeout using env variable")
}
// Setting custom client timeout for a new request
client.SetClientTimeout(time.Second * 20)
_ = client.NewRequest("PUT", "/")
if client.config.HttpClient.Timeout != time.Second*20 {
t.Fatalf("error setting client timeout using SetClientTimeout")
}
}
type roundTripperFunc func(*http.Request) (*http.Response, error)

View File

@ -843,9 +843,9 @@ func TestBackend_CertWrites(t *testing.T) {
tc := logicaltest.TestCase{
Backend: testFactory(t),
Steps: []logicaltest.TestStep{
testAccStepCert(t, "aaa", ca1, "foo", "", "", false),
testAccStepCert(t, "bbb", ca2, "foo", "", "", false),
testAccStepCert(t, "ccc", ca3, "foo", "", "", true),
testAccStepCert(t, "aaa", ca1, "foo", allowed{}, false),
testAccStepCert(t, "bbb", ca2, "foo", allowed{}, false),
testAccStepCert(t, "ccc", ca3, "foo", allowed{}, true),
},
}
tc.Steps = append(tc.Steps, testAccStepListCerts(t, []string{"aaa", "bbb"})...)
@ -866,7 +866,7 @@ func TestBackend_basic_CA(t *testing.T) {
logicaltest.Test(t, logicaltest.TestCase{
Backend: testFactory(t),
Steps: []logicaltest.TestStep{
testAccStepCert(t, "web", ca, "foo", "", "", false),
testAccStepCert(t, "web", ca, "foo", allowed{}, false),
testAccStepLogin(t, connState),
testAccStepCertLease(t, "web", ca, "foo"),
testAccStepCertTTL(t, "web", ca, "foo"),
@ -875,9 +875,9 @@ func TestBackend_basic_CA(t *testing.T) {
testAccStepLogin(t, connState),
testAccStepCertNoLease(t, "web", ca, "foo"),
testAccStepLoginDefaultLease(t, connState),
testAccStepCert(t, "web", ca, "foo", "*.example.com", "", false),
testAccStepCert(t, "web", ca, "foo", allowed{names: "*.example.com"}, false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", "*.invalid.com", "", false),
testAccStepCert(t, "web", ca, "foo", allowed{names: "*.invalid.com"}, false),
testAccStepLoginInvalid(t, connState),
},
})
@ -926,20 +926,45 @@ func TestBackend_basic_singleCert(t *testing.T) {
logicaltest.Test(t, logicaltest.TestCase{
Backend: testFactory(t),
Steps: []logicaltest.TestStep{
testAccStepCert(t, "web", ca, "foo", "", "", false),
testAccStepCert(t, "web", ca, "foo", allowed{}, false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", "example.com", "", false),
testAccStepCert(t, "web", ca, "foo", allowed{names: "example.com"}, false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", "invalid", "", false),
testAccStepCert(t, "web", ca, "foo", allowed{names: "invalid"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", "", "1.2.3.4:invalid", false),
testAccStepCert(t, "web", ca, "foo", allowed{ext: "1.2.3.4:invalid"}, false),
testAccStepLoginInvalid(t, connState),
},
})
}
// Test a self-signed client with custom extensions (root CA) that is trusted
func TestBackend_extensions_singleCert(t *testing.T) {
func TestBackend_common_name_singleCert(t *testing.T) {
connState, err := testConnState("test-fixtures/root/rootcacert.pem",
"test-fixtures/root/rootcakey.pem", "test-fixtures/root/rootcacert.pem")
if err != nil {
t.Fatalf("error testing connection state: %v", err)
}
ca, err := ioutil.ReadFile("test-fixtures/root/rootcacert.pem")
if err != nil {
t.Fatalf("err: %v", err)
}
logicaltest.Test(t, logicaltest.TestCase{
Backend: testFactory(t),
Steps: []logicaltest.TestStep{
testAccStepCert(t, "web", ca, "foo", allowed{}, false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{common_names: "example.com"}, false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{common_names: "invalid"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{ext: "1.2.3.4:invalid"}, false),
testAccStepLoginInvalid(t, connState),
},
})
}
// Test a self-signed client with custom ext (root CA) that is trusted
func TestBackend_ext_singleCert(t *testing.T) {
connState, err := testConnState(
"test-fixtures/root/rootcawextcert.pem",
"test-fixtures/root/rootcawextkey.pem",
@ -955,39 +980,132 @@ func TestBackend_extensions_singleCert(t *testing.T) {
logicaltest.Test(t, logicaltest.TestCase{
Backend: testFactory(t),
Steps: []logicaltest.TestStep{
testAccStepCert(t, "web", ca, "foo", "", "2.1.1.1:A UTF8String Extension", false),
testAccStepCert(t, "web", ca, "foo", allowed{ext: "2.1.1.1:A UTF8String Extension"}, false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", "", "2.1.1.1:*,2.1.1.2:A UTF8*", false),
testAccStepCert(t, "web", ca, "foo", allowed{ext: "2.1.1.1:*,2.1.1.2:A UTF8*"}, false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", "", "1.2.3.45:*", false),
testAccStepCert(t, "web", ca, "foo", allowed{ext: "1.2.3.45:*"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", "", "2.1.1.1:The Wrong Value", false),
testAccStepCert(t, "web", ca, "foo", allowed{ext: "2.1.1.1:The Wrong Value"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", "", "2.1.1.1:*,2.1.1.2:The Wrong Value", false),
testAccStepCert(t, "web", ca, "foo", allowed{ext: "2.1.1.1:*,2.1.1.2:The Wrong Value"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", "", "2.1.1.1:", false),
testAccStepCert(t, "web", ca, "foo", allowed{ext: "2.1.1.1:"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", "", "2.1.1.1:,2.1.1.2:*", false),
testAccStepCert(t, "web", ca, "foo", allowed{ext: "2.1.1.1:,2.1.1.2:*"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", "example.com", "2.1.1.1:A UTF8String Extension", false),
testAccStepCert(t, "web", ca, "foo", allowed{names: "example.com", ext: "2.1.1.1:A UTF8String Extension"}, false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", "example.com", "2.1.1.1:*,2.1.1.2:A UTF8*", false),
testAccStepCert(t, "web", ca, "foo", allowed{names: "example.com", ext: "2.1.1.1:*,2.1.1.2:A UTF8*"}, false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", "example.com", "1.2.3.45:*", false),
testAccStepCert(t, "web", ca, "foo", allowed{names: "example.com", ext: "1.2.3.45:*"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", "example.com", "2.1.1.1:The Wrong Value", false),
testAccStepCert(t, "web", ca, "foo", allowed{names: "example.com", ext: "2.1.1.1:The Wrong Value"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", "example.com", "2.1.1.1:*,2.1.1.2:The Wrong Value", false),
testAccStepCert(t, "web", ca, "foo", allowed{names: "example.com", ext: "2.1.1.1:*,2.1.1.2:The Wrong Value"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", "invalid", "2.1.1.1:A UTF8String Extension", false),
testAccStepCert(t, "web", ca, "foo", allowed{names: "invalid", ext: "2.1.1.1:A UTF8String Extension"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", "invalid", "2.1.1.1:*,2.1.1.2:A UTF8*", false),
testAccStepCert(t, "web", ca, "foo", allowed{names: "invalid", ext: "2.1.1.1:*,2.1.1.2:A UTF8*"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", "invalid", "1.2.3.45:*", false),
testAccStepCert(t, "web", ca, "foo", allowed{names: "invalid", ext: "1.2.3.45:*"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", "invalid", "2.1.1.1:The Wrong Value", false),
testAccStepCert(t, "web", ca, "foo", allowed{names: "invalid", ext: "2.1.1.1:The Wrong Value"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", "invalid", "2.1.1.1:*,2.1.1.2:The Wrong Value", false),
testAccStepCert(t, "web", ca, "foo", allowed{names: "invalid", ext: "2.1.1.1:*,2.1.1.2:The Wrong Value"}, false),
testAccStepLoginInvalid(t, connState),
},
})
}
// Test a self-signed client with URI alt names (root CA) that is trusted
func TestBackend_dns_singleCert(t *testing.T) {
connState, err := testConnState(
"test-fixtures/root/rootcawdnscert.pem",
"test-fixtures/root/rootcawdnskey.pem",
"test-fixtures/root/rootcacert.pem",
)
if err != nil {
t.Fatalf("error testing connection state: %v", err)
}
ca, err := ioutil.ReadFile("test-fixtures/root/rootcacert.pem")
if err != nil {
t.Fatalf("err: %v", err)
}
logicaltest.Test(t, logicaltest.TestCase{
Backend: testFactory(t),
Steps: []logicaltest.TestStep{
testAccStepCert(t, "web", ca, "foo", allowed{dns: "example.com"}, false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{dns: "*ample.com"}, false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{dns: "notincert.com"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{dns: "abc"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{dns: "*.example.com"}, false),
testAccStepLoginInvalid(t, connState),
},
})
}
// Test a self-signed client with URI alt names (root CA) that is trusted
func TestBackend_email_singleCert(t *testing.T) {
connState, err := testConnState(
"test-fixtures/root/rootcawemailcert.pem",
"test-fixtures/root/rootcawemailkey.pem",
"test-fixtures/root/rootcacert.pem",
)
if err != nil {
t.Fatalf("error testing connection state: %v", err)
}
ca, err := ioutil.ReadFile("test-fixtures/root/rootcacert.pem")
if err != nil {
t.Fatalf("err: %v", err)
}
logicaltest.Test(t, logicaltest.TestCase{
Backend: testFactory(t),
Steps: []logicaltest.TestStep{
testAccStepCert(t, "web", ca, "foo", allowed{emails: "valid@example.com"}, false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{emails: "*@example.com"}, false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{emails: "invalid@notincert.com"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{emails: "abc"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{emails: "*.example.com"}, false),
testAccStepLoginInvalid(t, connState),
},
})
}
// Test a self-signed client with URI alt names (root CA) that is trusted
func TestBackend_uri_singleCert(t *testing.T) {
connState, err := testConnState(
"test-fixtures/root/rootcawuricert.pem",
"test-fixtures/root/rootcawurikey.pem",
"test-fixtures/root/rootcacert.pem",
)
if err != nil {
t.Fatalf("error testing connection state: %v", err)
}
ca, err := ioutil.ReadFile("test-fixtures/root/rootcacert.pem")
if err != nil {
t.Fatalf("err: %v", err)
}
logicaltest.Test(t, logicaltest.TestCase{
Backend: testFactory(t),
Steps: []logicaltest.TestStep{
testAccStepCert(t, "web", ca, "foo", allowed{uris: "spiffe://example.com/*"}, false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{uris: "spiffe://example.com/host"}, false),
testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{uris: "spiffe://example.com/invalid"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{uris: "abc"}, false),
testAccStepLoginInvalid(t, connState),
testAccStepCert(t, "web", ca, "foo", allowed{uris: "http://www.google.com"}, false),
testAccStepLoginInvalid(t, connState),
},
})
@ -1007,9 +1125,9 @@ func TestBackend_mixed_constraints(t *testing.T) {
logicaltest.Test(t, logicaltest.TestCase{
Backend: testFactory(t),
Steps: []logicaltest.TestStep{
testAccStepCert(t, "1unconstrained", ca, "foo", "", "", false),
testAccStepCert(t, "2matching", ca, "foo", "*.example.com,whatever", "", false),
testAccStepCert(t, "3invalid", ca, "foo", "invalid", "", false),
testAccStepCert(t, "1unconstrained", ca, "foo", allowed{}, false),
testAccStepCert(t, "2matching", ca, "foo", allowed{names: "*.example.com,whatever"}, false),
testAccStepCert(t, "3invalid", ca, "foo", allowed{names: "invalid"}, false),
testAccStepLogin(t, connState),
// Assumes CertEntries are processed in alphabetical order (due to store.List), so we only match 2matching if 1unconstrained doesn't match
testAccStepLoginWithName(t, connState, "2matching"),
@ -1314,19 +1432,32 @@ func testAccStepListCerts(
}
}
type allowed struct {
names string // allowed names in the certificate, looks at common, name, dns, email [depricated]
common_names string // allowed common names in the certificate
dns string // allowed dns names in the SAN extension of the certificate
emails string // allowed email names in SAN extension of the certificate
uris string // allowed uris in SAN extension of the certificate
ext string // required extensions in the certificate
}
func testAccStepCert(
t *testing.T, name string, cert []byte, policies string, allowedNames string, requiredExtensions string, expectError bool) logicaltest.TestStep {
t *testing.T, name string, cert []byte, policies string, testData allowed, expectError bool) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.UpdateOperation,
Path: "certs/" + name,
ErrorOk: expectError,
Data: map[string]interface{}{
"certificate": string(cert),
"policies": policies,
"display_name": name,
"allowed_names": allowedNames,
"required_extensions": requiredExtensions,
"lease": 1000,
"certificate": string(cert),
"policies": policies,
"display_name": name,
"allowed_names": testData.names,
"allowed_common_names": testData.common_names,
"allowed_dns_sans": testData.dns,
"allowed_email_sans": testData.emails,
"allowed_uri_sans": testData.uris,
"required_extensions": testData.ext,
"lease": 1000,
},
Check: func(resp *logical.Response) error {
if resp == nil && expectError {

View File

@ -45,7 +45,33 @@ Must be x509 PEM encoded.`,
"allowed_names": &framework.FieldSchema{
Type: framework.TypeCommaStringSlice,
Description: `A comma-separated list of names.
At least one must exist in either the Common Name or SANs. Supports globbing.`,
At least one must exist in either the Common Name or SANs. Supports globbing.
This parameter is deprecated, please use allowed_common_names, allowed_dns_sans,
allowed_email_sans, allowed_uri_sans.`,
},
"allowed_common_names": &framework.FieldSchema{
Type: framework.TypeCommaStringSlice,
Description: `A comma-separated list of names.
At least one must exist in the Common Name. Supports globbing.`,
},
"allowed_dns_sans": &framework.FieldSchema{
Type: framework.TypeCommaStringSlice,
Description: `A comma-separated list of DNS names.
At least one must exist in the SANs. Supports globbing.`,
},
"allowed_email_sans": &framework.FieldSchema{
Type: framework.TypeCommaStringSlice,
Description: `A comma-separated list of Email Addresses.
At least one must exist in the SANs. Supports globbing.`,
},
"allowed_uri_sans": &framework.FieldSchema{
Type: framework.TypeCommaStringSlice,
Description: `A comma-separated list of URIs.
At least one must exist in the SANs. Supports globbing.`,
},
"required_extensions": &framework.FieldSchema{
@ -77,12 +103,14 @@ seconds. Defaults to system/backend default TTL.`,
Description: `TTL for tokens issued by this backend.
Defaults to system/backend default TTL time.`,
},
"max_ttl": &framework.FieldSchema{
Type: framework.TypeDurationSecond,
Description: `Duration in either an integer number of seconds (3600) or
an integer time unit (60m) after which the
issued token can no longer be renewed.`,
},
"period": &framework.FieldSchema{
Type: framework.TypeDurationSecond,
Description: `If set, indicates that the token generated using this role
@ -151,13 +179,18 @@ func (b *backend) pathCertRead(ctx context.Context, req *logical.Request, d *fra
return &logical.Response{
Data: map[string]interface{}{
"certificate": cert.Certificate,
"display_name": cert.DisplayName,
"policies": cert.Policies,
"ttl": cert.TTL / time.Second,
"max_ttl": cert.MaxTTL / time.Second,
"period": cert.Period / time.Second,
"allowed_names": cert.AllowedNames,
"certificate": cert.Certificate,
"display_name": cert.DisplayName,
"policies": cert.Policies,
"ttl": cert.TTL / time.Second,
"max_ttl": cert.MaxTTL / time.Second,
"period": cert.Period / time.Second,
"allowed_names": cert.AllowedNames,
"allowed_common_names": cert.AllowedCommonNames,
"allowed_dns_sans": cert.AllowedDNSSANs,
"allowed_email_sans": cert.AllowedEmailSANs,
"allowed_uri_sans": cert.AllowedURISANs,
"required_extensions": cert.RequiredExtensions,
},
}, nil
}
@ -168,6 +201,10 @@ func (b *backend) pathCertWrite(ctx context.Context, req *logical.Request, d *fr
displayName := d.Get("display_name").(string)
policies := policyutil.ParsePolicies(d.Get("policies"))
allowedNames := d.Get("allowed_names").([]string)
allowedCommonNames := d.Get("allowed_common_names").([]string)
allowedDNSSANs := d.Get("allowed_dns_sans").([]string)
allowedEmailSANs := d.Get("allowed_email_sans").([]string)
allowedURISANs := d.Get("allowed_uri_sans").([]string)
requiredExtensions := d.Get("required_extensions").([]string)
var resp logical.Response
@ -246,6 +283,10 @@ func (b *backend) pathCertWrite(ctx context.Context, req *logical.Request, d *fr
DisplayName: displayName,
Policies: policies,
AllowedNames: allowedNames,
AllowedCommonNames: allowedCommonNames,
AllowedDNSSANs: allowedDNSSANs,
AllowedEmailSANs: allowedEmailSANs,
AllowedURISANs: allowedURISANs,
RequiredExtensions: requiredExtensions,
TTL: ttl,
MaxTTL: maxTTL,
@ -278,6 +319,10 @@ type CertEntry struct {
MaxTTL time.Duration
Period time.Duration
AllowedNames []string
AllowedCommonNames []string
AllowedDNSSANs []string
AllowedEmailSANs []string
AllowedURISANs []string
RequiredExtensions []string
BoundCIDRs []*sockaddr.SockAddrMarshaler
}

View File

@ -253,6 +253,10 @@ func (b *backend) verifyCredentials(ctx context.Context, req *logical.Request, d
func (b *backend) matchesConstraints(clientCert *x509.Certificate, trustedChain []*x509.Certificate, config *ParsedCert) bool {
return !b.checkForChainInCRLs(trustedChain) &&
b.matchesNames(clientCert, config) &&
b.matchesCommonName(clientCert, config) &&
b.matchesDNSSANs(clientCert, config) &&
b.matchesEmailSANs(clientCert, config) &&
b.matchesURISANs(clientCert, config) &&
b.matchesCertificateExtensions(clientCert, config)
}
@ -280,10 +284,85 @@ func (b *backend) matchesNames(clientCert *x509.Certificate, config *ParsedCert)
return true
}
}
}
return false
}
// matchesCommonName verifies that the certificate matches at least one configured
// allowed common name
func (b *backend) matchesCommonName(clientCert *x509.Certificate, config *ParsedCert) bool {
// Default behavior (no names) is to allow all names
if len(config.Entry.AllowedCommonNames) == 0 {
return true
}
// At least one pattern must match at least one name if any patterns are specified
for _, allowedCommonName := range config.Entry.AllowedCommonNames {
if glob.Glob(allowedCommonName, clientCert.Subject.CommonName) {
return true
}
}
return false
}
// matchesDNSSANs verifies that the certificate matches at least one configured
// allowed dns entry in the subject alternate name extension
func (b *backend) matchesDNSSANs(clientCert *x509.Certificate, config *ParsedCert) bool {
// Default behavior (no names) is to allow all names
if len(config.Entry.AllowedDNSSANs) == 0 {
return true
}
// At least one pattern must match at least one name if any patterns are specified
for _, allowedDNS := range config.Entry.AllowedDNSSANs {
for _, name := range clientCert.DNSNames {
if glob.Glob(allowedDNS, name) {
return true
}
}
}
return false
}
// matchesEmailSANs verifies that the certificate matches at least one configured
// allowed email in the subject alternate name extension
func (b *backend) matchesEmailSANs(clientCert *x509.Certificate, config *ParsedCert) bool {
// Default behavior (no names) is to allow all names
if len(config.Entry.AllowedEmailSANs) == 0 {
return true
}
// At least one pattern must match at least one name if any patterns are specified
for _, allowedEmail := range config.Entry.AllowedEmailSANs {
for _, email := range clientCert.EmailAddresses {
if glob.Glob(allowedEmail, email) {
return true
}
}
}
return false
}
// matchesURISANs verifies that the certificate matches at least one configured
// allowed uri in the subject alternate name extension
func (b *backend) matchesURISANs(clientCert *x509.Certificate, config *ParsedCert) bool {
// Default behavior (no names) is to allow all names
if len(config.Entry.AllowedURISANs) == 0 {
return true
}
// At least one pattern must match at least one name if any patterns are specified
for _, allowedURI := range config.Entry.AllowedURISANs {
for _, name := range clientCert.URIs {
if glob.Glob(allowedURI, name.String()) {
return true
}
}
}
return false
}
// matchesCertificateExtensions verifies that the certificate matches configured
// required extensions
func (b *backend) matchesCertificateExtensions(clientCert *x509.Certificate, config *ParsedCert) bool {

View File

@ -1 +1 @@
92223EAFBBEE17A3
92223EAFBBEE17AF

View File

@ -0,0 +1,17 @@
[ req ]
default_bits = 2048
encrypt_key = no
prompt = no
default_md = sha256
req_extensions = req_v3
distinguished_name = dn
[ dn ]
CN = example.com
[ req_v3 ]
subjectAltName = @alt_names
[ alt_names ]
IP.1 = 127.0.0.1
DNS.1 = example.com

View File

@ -0,0 +1,27 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIEijCCAnICAQAwFjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wggIiMA0GCSqGSIb3
DQEBAQUAA4ICDwAwggIKAoICAQDUJ6s97BFxR295bCjpwQ85Vo8DnBFa/awNH107
QFn/zw0ZDdJMLtEBc/bw7pTYw5ulKbiZDFrmzPEY+QZlo+t1TeWgPRJg0CbYNukS
aNv0vKXjDXYwbrCyOvZucy8hte6IKjZfH+kAsgbbUxfD75BCKsxMxbVHkg0W9Ma2
pnZj/kpvQE5lkMj5mDvtWdfCRsVg4zL6jhRHkPZ6fOkF3mrfTbQu3oyOcbKLEE/G
t3QRKw3uv0vMDmhg62ZPvD1k70UMjUV2MVqEPZuWY7/bbW8OsfzMyBOGY9LLp7QS
krxWYRj6SPUR4f1bZq7pRbqOfS0okq/XDLf1k6Na5cT6iNdyjEVdSJl7vR7kSreX
8hkwK46Oup8v/vJLu/cRDCpAas0gJJkJDPt5114V0/Xww7EFxs5GijXP8i5RLlgK
/nRscbK+fgjQOnQ5cp0pcP8HAriy2vil7E0fQvMvt5QTyINEYgiYaCIT9WGRC8Xo
WcoUGI2vyrGy6RU6A3/TKeBLtikaSPjFKa1dFTAHfrUkTBpfqc+sbiJ334Bvucg5
WyS8oAC5Vf++iMnETSdzx1k0/QARVLD38PO8wPaPU1M2XaSA+RHTB9SGFc4VTauT
B167NLlmgJHYuhp+KM1RTy1TEoDlJh2qKj21BLcR1GJ0KgDze6Vpf9xdRTdqMpo2
h20wdQIDAQABoC8wLQYJKoZIhvcNAQkOMSAwHjAcBgNVHREEFTAThwR/AAABggtl
eGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAgEAGtds+2IUVKzw9bi130mBbb9K
CrXw2NXSp+LJwneIEy0bjAaNr7zCGQsj7q57qFwjc7vLTtGRheP5myrAOq00lp8J
1sGZSETS/y4yeITLZSYWVq2dtF/hY9I+X3uOoibdsQgzYqhBcUr4oTDapf1ZEs0i
wA2J5IcasfaBpWFc9wRN79BBACLGyCbX6VwUISrGe3Hgzkeqtg97cU62ecQsgXiZ
LdQgERvC0wSfAmI4lGulXi1oYYSRxXQ8pEKEAMeJrJVQfvhdbS/o4Bdf3Yj6ibtD
tFSdKLcdRCfMQBEHNpSh665LfBbwU55Fh89tBdGmf6uqsimUY6AxNncnLsc1Kq6F
oINXix3GsBNmCahDeHdGOlNjw0Lpl0m6bnu6LXSDwwuNWAEdDfEmxR+5T/GkGxcG
TTWPwEkpnCe4VmGl9Y10uPSvqneNsdNWjDVK4BeW4VSf9Lp1Zeme1dYFvpyzow+r
4ogpvMPf5vy5I/0HCEf1KlaPyhs8ZGK6YBGaeEDYSaysAWJfYm8eiqwUuKYj/FUe
G3KkaFpOGsQHFNRtG8GukV3r2AK97HFHKNfygZ2xvk5isXz2ZsNX1/J0+GGjalJl
cWBBEiXFM94XJHE9rACsL2UKn8cWCh9lHNLlePOkQuoNY9CUd63xx4Hg97XWP3+U
DhpG7CADsKcPJfbMgrk=
-----END CERTIFICATE REQUEST-----

View File

@ -0,0 +1,23 @@
-----BEGIN CERTIFICATE-----
MIIDzzCCAregAwIBAgIJAJIiPq+77herMA0GCSqGSIb3DQEBBQUAMBYxFDASBgNV
BAMTC2V4YW1wbGUuY29tMB4XDTE4MDQyNjEyMDEzMFoXDTE5MDQyNjEyMDEzMFow
FjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw
ggIKAoICAQDUJ6s97BFxR295bCjpwQ85Vo8DnBFa/awNH107QFn/zw0ZDdJMLtEB
c/bw7pTYw5ulKbiZDFrmzPEY+QZlo+t1TeWgPRJg0CbYNukSaNv0vKXjDXYwbrCy
OvZucy8hte6IKjZfH+kAsgbbUxfD75BCKsxMxbVHkg0W9Ma2pnZj/kpvQE5lkMj5
mDvtWdfCRsVg4zL6jhRHkPZ6fOkF3mrfTbQu3oyOcbKLEE/Gt3QRKw3uv0vMDmhg
62ZPvD1k70UMjUV2MVqEPZuWY7/bbW8OsfzMyBOGY9LLp7QSkrxWYRj6SPUR4f1b
Zq7pRbqOfS0okq/XDLf1k6Na5cT6iNdyjEVdSJl7vR7kSreX8hkwK46Oup8v/vJL
u/cRDCpAas0gJJkJDPt5114V0/Xww7EFxs5GijXP8i5RLlgK/nRscbK+fgjQOnQ5
cp0pcP8HAriy2vil7E0fQvMvt5QTyINEYgiYaCIT9WGRC8XoWcoUGI2vyrGy6RU6
A3/TKeBLtikaSPjFKa1dFTAHfrUkTBpfqc+sbiJ334Bvucg5WyS8oAC5Vf++iMnE
TSdzx1k0/QARVLD38PO8wPaPU1M2XaSA+RHTB9SGFc4VTauTB167NLlmgJHYuhp+
KM1RTy1TEoDlJh2qKj21BLcR1GJ0KgDze6Vpf9xdRTdqMpo2h20wdQIDAQABoyAw
HjAcBgNVHREEFTAThwR/AAABggtleGFtcGxlLmNvbTANBgkqhkiG9w0BAQUFAAOC
AQEA2JswcCYtHvOm2QmSEVeFcCeVNkzr35FXATamJv0oMMjjUFix78MW03EW6vJa
E52e3pBvRdy+k2fuq/RtHIUKzB6jNbv0Vds26Dq+pmGeoaQZOW94/Wht7f9pZgBi
IRPBg9oACtyNAuDsCOdetOyvyoU29sjUOUoQZbEXF+FK4lRJrEmZUJHbp/BOD58V
mQRtjTMjQlZZropqBQmooMRYU0qgWHaIjyoQpu2MgEj3+/1b1IX6SCfRuit0auh/
YI3/cCtyAG/DpZ6zfyXuyY+iN+l8B6t0nXyV3g8JgBWYPGJv1hgVIgnnqlwuL517
mEAT5RnHCNJQNuzS1dwfuBrX3w==
-----END CERTIFICATE-----

View File

@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDUJ6s97BFxR295
bCjpwQ85Vo8DnBFa/awNH107QFn/zw0ZDdJMLtEBc/bw7pTYw5ulKbiZDFrmzPEY
+QZlo+t1TeWgPRJg0CbYNukSaNv0vKXjDXYwbrCyOvZucy8hte6IKjZfH+kAsgbb
UxfD75BCKsxMxbVHkg0W9Ma2pnZj/kpvQE5lkMj5mDvtWdfCRsVg4zL6jhRHkPZ6
fOkF3mrfTbQu3oyOcbKLEE/Gt3QRKw3uv0vMDmhg62ZPvD1k70UMjUV2MVqEPZuW
Y7/bbW8OsfzMyBOGY9LLp7QSkrxWYRj6SPUR4f1bZq7pRbqOfS0okq/XDLf1k6Na
5cT6iNdyjEVdSJl7vR7kSreX8hkwK46Oup8v/vJLu/cRDCpAas0gJJkJDPt5114V
0/Xww7EFxs5GijXP8i5RLlgK/nRscbK+fgjQOnQ5cp0pcP8HAriy2vil7E0fQvMv
t5QTyINEYgiYaCIT9WGRC8XoWcoUGI2vyrGy6RU6A3/TKeBLtikaSPjFKa1dFTAH
frUkTBpfqc+sbiJ334Bvucg5WyS8oAC5Vf++iMnETSdzx1k0/QARVLD38PO8wPaP
U1M2XaSA+RHTB9SGFc4VTauTB167NLlmgJHYuhp+KM1RTy1TEoDlJh2qKj21BLcR
1GJ0KgDze6Vpf9xdRTdqMpo2h20wdQIDAQABAoICAQDJxszUQQC554I7TsZ+xBJx
q0Sr3zSWgOuxM2Jdpy+x38AKUx3vPRulsSBtN8yzeR9Ab7TVQ231U3f/E2GlK8kW
sTazN0KSd4ZqX5c+3iJM21s+3p/JIo3FhdS5aa2q9zjdoqBByry135wr3xScUu22
MLRMVEG8x0jRy45vS1UQd1teAiBN8u1ijgp5DNjrOpohMxVaPeVFx7bU+pY58bdd
mK7FYP73v2VbY/EsA3FNntBKgQBbHFzjyR9uuI7/v53BeV9WMUxwt5OR7l8cGDHn
HRtdvPDtAWYMMf1PKOYdlY3HBbqn/nMUCk5TKPFs8dsQWqsI8lzIIVndauj0i0+0
M/lVMXu4x48o5FfLa4HjkpcDxAU6QDHA9thaDkasZebixVH/p1ZJkLORl5jDLYkU
Av+B3i1efITwNYgosZNjPpw0PyYh9PV9JvB87d5wFpgISfZyRXpBVGeJbt6gg++8
8/5A/GzSpGy0FhLcP3vuVTcX2VOexjqeaoi4U3cHrbWv/wNj5a4BNk5EJT8fVeSb
+Emqydl9u3n2E315GPC8kwxdE3r3hGrWdZQn9byGvqzwDaLWXQLQWvQN4GOpGTrP
Yxj2Oi8s1MJHkppj4eo52O4J7cBlAJn3RFmlCKGOoWJZMdPktp/gWeT+xIGSaa21
qB+l/ZFEWLPMxdTBMGFmYQKCAQEA8DgozaZBXr7mb1H25EbW9YmtLc61JMapQrZb
ObygiGR6RZsxCXEvGdhvvmwpO8rA9wAOWqI8NV5GU8EvuRuwvGoX4HqbXkB6ZcyC
6RuZzki2lrKVGUaLc1v6MyhX4IzrqTYWDgQvwd9lMcUGR7r007KPE5ft4v3/TuxQ
qPKxQE7NO2xnTloUchd5g0/d975GZi0g6XDecFOuj43Pi0c/wRcFH6zfVirdcm+M
yP9CsJ/LUgtV1voLqyhfybwHvzpxJ0l25Fw+P85I4czosBp+FaFAwogxZEDnY8Fr
Hqcwdc7vwGDjTbtflDsUdppt2h8nD8bBZGysG8+P8HAt3i5D+QKCAQEA4heKueRQ
Y8nTZlmRSRtB6usRAeymQBJjO+yWwu/06Efg8VW5QRwtP0sx+syrLaQDy8MT07II
XQZmq55xATWbHCxULiceIY2KG5LHCovVotYAll8ov58exJva19C7/41uVrkl3H9j
xFLX0Bn3zMFKBOxKhygP2xqqEJdb1JJt27c2CbXvXOzqIZ4RCaNQdBdrlEiXQihR
JCGMUBfrYIwALQFzYuPGULhg77YcAi5owCPnfK+dDOOvMmW8BwPnRUc14WFIVV+m
dbY22WonLNPP055W5755Xl9RHKW1bcmIH6E4QZpMrlnd1UzPBQq1PJtcO3uRc5T6
CMQSUmwMGSQ3XQKCAQBiuVHborY+8AnYOkFTc+GoK5rmtosvwA2UA0neoqz/IPw3
Wx5+GOwYnSDfi6gukJdZa8Z6bS59aG9SwJSSaNTrulZxxTHRPIKRD8nFb7h4VN3l
dSNdreZl1KkxGSV0fbXkZvwNap8N+HeoSqbYF/fCgSHYFZqIrYadsvU7WfKK0Vf7
UgPq6Y55jTg9RTeeN67LE0Txa5efZmTZTpi7Tt7exk0uxWdMDHXSMBIWEQIhgKqY
31u57C2bfA5R5FrytlwGn2SjWV2j7214jzQaG+kxjoIE8OALqbjvAHC7uk5qPE/A
KpGAQr93Ngik7baz7BWroC2eziK1k0o+sHvJUg5RAoIBABF+ftZ5axr9j+T4gzxj
5orV24AJnqeQhKsrWFMHHC0o+qfR2T7HflzKZbihQ5GJgl2u34be3LTN/P3Eibvt
OO5KI81aa4NvH0OY7NvNDB/IbU01WcLR/iB6asmONi3E9MezFdHk7YRQYLCSgdEP
F7ofyniAyhFLE+OqwolFN0jr+TtxH29SSZ+GSo0zXNNOyJ01rLaKxhSEoAXGhAj5
bD4PQa1iMIMocR+7OJmWm7ZaUNwd/onzyCefJZhpXejHZMzmqSEqAIhVLBNQmm1m
iks2kkTmQR/jQjR0QgCXunewEtlIpixLedW6Vr5uIK3q240it5N48IvjGAPWpmz/
l2UCggEBALRlARlBdYcPhWbcyhq9suq2PJqHVvNdP9vVTZpPgmWgxmj9mPQ731Q/
UpRlBIk6U0qAxBXP9kzPqSivWhY8Jto80Jdr+/80PkdANDkpnXoDlxPk095sD2uN
Jv5FffFgMZH9MGpPTEuZ571/YtVi+1qFt0i3oazpF/g8gU23f2oxaX4xzsltVl8J
rWXYzmYE0i5Qiy81+zZ9dZlnmlKhcYpD6m2t/0hRAoNaoxOUV7WFcIzYIxpKvzYL
QTDL/Se2Ooc0xLQvM1oZ9/1NE2hpGQ/ipASEPlx9KO5ktYW7+LwdcSCMXtx84I/D
VQpWjPdILMpiVrB/9NsENTNv2DUvc+o=
-----END PRIVATE KEY-----

View File

@ -0,0 +1,17 @@
[ req ]
default_bits = 2048
encrypt_key = no
prompt = no
default_md = sha256
distinguished_name = dn
req_extensions = req_v3
[ req_v3 ]
subjectAltName = @alt_names
[ dn ]
CN = example.com
[ alt_names ]
IP.1 = 127.0.0.1
email = valid@example.com

View File

@ -0,0 +1,27 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIEkDCCAngCAQAwFjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wggIiMA0GCSqGSIb3
DQEBAQUAA4ICDwAwggIKAoICAQDO7stcdJQwUtVeJriwpAswDAirO827peSlgdxs
fW8X9M2hE9ihvESEILb7TRMRiFlDyQYg1BxKMrJ0DZmixFi8RvUCZbH6TFOUMsk+
w1FhpzjuqAqxNQ51s7u30sfruJg7XN3YJLEPelom62wvzhvLXJFLQZlQCDrMx+PC
ofWs4IA7jR8JaXZjIGdkEU0GgRPy8zKPUe3dUBHi2UR4eKT4cRCn4IwCrFx4BQjV
AxNKNDGpe+fTVOzII/UX+FppDdGZZ4g0y3E1mQUEKkff4dKCK7vhlGJR9D+5v/V0
/stwP72aXczijuVtnXXyli+oj24NaijoqQluNCD3MvV/INovLL2Tyk54H3/GvpU1
+sJbpE2+UPh+Rh8DNkT6RPRguymJO8MSsdLt/qvVD8BlZ7I9V3XZlDKosCRTUyxf
jjFpa+VzB3nt7uFtIXZ9HNGhQIpOULvkFGizWV+tS8PpGdTFVzDjyWg0HUKWn8g8
IiWR9S40h6mHjVuTuxA9tlO69PuTjGK7MlAvFTDaPC8seau1LUiqtQ+prnSLI0h1
6GfI9W2G7BKKVPloErODhLcsOcwRcmaJVW+yBda3te8+cMBIvtQYKAYSCtg8qXws
xyfPLo4GChbGGRbRCuM3mB1lG1qHEivJ0vynsgolp0t8jaXSFVBVgYj+C6Vd9/hl
ieUcOwIDAQABoDUwMwYJKoZIhvcNAQkOMSYwJDAiBgNVHREEGzAZhwR/AAABgRF2
YWxpZEBleGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAgEAe1u3wYMMKaZ3d5Wz
jKH971CF3sl+KYl+oV0ekD8dbYe+szERgajn6Y5IYVxxi5o9UgeAWqnyHCiqsW8T
MQdUwMa67Ym/pHKSVoBlGePAKHqock0+iSsVBMcPpU9RkxdSW2aVtdb0DGfyB952
t3dSb0LaITu30fe8p7lxrL0DKESbwd4N2XQE1F5Vf+1OodvpJinn4Wqzn45hqRf0
imxrCgVjT5VtR+NRzKCK3Msmh+cJGpR3zgXwGKqgHLWzhvSoQwRWYE3apMK5xLk7
N1sWVxEKK5+L/CDaMNGQFx5lPiCN3bUudCq4uSZcPLO5AuDpSeLKnknBrWA6HcbB
WvnwtKmHeVe2qogPViKGuwE16rnPlp9hysPl2ckmtqEsXRagIAh5fMI3OoRbZmVV
jfJm21U4YkUWuMKet3EU1StT6T8T6O7QEFA4w4s5+m3dsjDZ9iTuK9/dCs1xnIke
4uJYmh3YrNl8IjMffJuWxA+/de3JO1UljC2EAFxa5KAc24+qyeWwky4tMv72gTOp
6q3k2wnsrK5B1errRV37OLgxtoh1I3Rgp+0B77SOK/PpD/JJazJG5O9bBJOvHJc0
STW9Td2CzgC2lKGfvkX6UYgVy/9HDq7/EKXP/G2f3kRik2NPUhGcnAH9nyL9SvpP
+T4CZ+FumDj5DulARk6arSq+uy4=
-----END CERTIFICATE REQUEST-----

View File

@ -0,0 +1,23 @@
-----BEGIN CERTIFICATE-----
MIID1TCCAr2gAwIBAgIJAJIiPq+77hevMA0GCSqGSIb3DQEBBQUAMBYxFDASBgNV
BAMTC2V4YW1wbGUuY29tMB4XDTE4MDQyNjEyMjE1MloXDTE5MDQyNjEyMjE1Mlow
FjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw
ggIKAoICAQDO7stcdJQwUtVeJriwpAswDAirO827peSlgdxsfW8X9M2hE9ihvESE
ILb7TRMRiFlDyQYg1BxKMrJ0DZmixFi8RvUCZbH6TFOUMsk+w1FhpzjuqAqxNQ51
s7u30sfruJg7XN3YJLEPelom62wvzhvLXJFLQZlQCDrMx+PCofWs4IA7jR8JaXZj
IGdkEU0GgRPy8zKPUe3dUBHi2UR4eKT4cRCn4IwCrFx4BQjVAxNKNDGpe+fTVOzI
I/UX+FppDdGZZ4g0y3E1mQUEKkff4dKCK7vhlGJR9D+5v/V0/stwP72aXczijuVt
nXXyli+oj24NaijoqQluNCD3MvV/INovLL2Tyk54H3/GvpU1+sJbpE2+UPh+Rh8D
NkT6RPRguymJO8MSsdLt/qvVD8BlZ7I9V3XZlDKosCRTUyxfjjFpa+VzB3nt7uFt
IXZ9HNGhQIpOULvkFGizWV+tS8PpGdTFVzDjyWg0HUKWn8g8IiWR9S40h6mHjVuT
uxA9tlO69PuTjGK7MlAvFTDaPC8seau1LUiqtQ+prnSLI0h16GfI9W2G7BKKVPlo
ErODhLcsOcwRcmaJVW+yBda3te8+cMBIvtQYKAYSCtg8qXwsxyfPLo4GChbGGRbR
CuM3mB1lG1qHEivJ0vynsgolp0t8jaXSFVBVgYj+C6Vd9/hlieUcOwIDAQABoyYw
JDAiBgNVHREEGzAZhwR/AAABgRF2YWxpZEBleGFtcGxlLmNvbTANBgkqhkiG9w0B
AQUFAAOCAQEAp2T99t93hxPyCDaqfTF0lsdzIgxZ5GkSzYTYQ2pekLfMDUUy4WFQ
AppdnSJSpm6b+xWO2DkO8UAgOdSEORf/Qpfm+UpHaEYZlQiWQ0zNmIQgBoh6indU
bEZKeL6aAOfIshPNfmqjFt+DpEClrQvCHJggG/rB77Ujj6hPY2+8h4JjbjeX7Pe9
oUEx9LpZ5Qpo6PK5vB537PP7Q2qp2PIr29DLz1VeLCbqUnV+j7qT0T3hhqurnpTA
QUiRZI0etgeP/B5lw/S4AWijq+R6RasdPAS4UNHsYK+PSGiqdhW/bJvSx5UBXQbk
DuY2A4kdv60H5Aw45/F6enH2Fg1kg7PlQA==
-----END CERTIFICATE-----

View File

@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
MIIJRAIBADANBgkqhkiG9w0BAQEFAASCCS4wggkqAgEAAoICAQDO7stcdJQwUtVe
JriwpAswDAirO827peSlgdxsfW8X9M2hE9ihvESEILb7TRMRiFlDyQYg1BxKMrJ0
DZmixFi8RvUCZbH6TFOUMsk+w1FhpzjuqAqxNQ51s7u30sfruJg7XN3YJLEPelom
62wvzhvLXJFLQZlQCDrMx+PCofWs4IA7jR8JaXZjIGdkEU0GgRPy8zKPUe3dUBHi
2UR4eKT4cRCn4IwCrFx4BQjVAxNKNDGpe+fTVOzII/UX+FppDdGZZ4g0y3E1mQUE
Kkff4dKCK7vhlGJR9D+5v/V0/stwP72aXczijuVtnXXyli+oj24NaijoqQluNCD3
MvV/INovLL2Tyk54H3/GvpU1+sJbpE2+UPh+Rh8DNkT6RPRguymJO8MSsdLt/qvV
D8BlZ7I9V3XZlDKosCRTUyxfjjFpa+VzB3nt7uFtIXZ9HNGhQIpOULvkFGizWV+t
S8PpGdTFVzDjyWg0HUKWn8g8IiWR9S40h6mHjVuTuxA9tlO69PuTjGK7MlAvFTDa
PC8seau1LUiqtQ+prnSLI0h16GfI9W2G7BKKVPloErODhLcsOcwRcmaJVW+yBda3
te8+cMBIvtQYKAYSCtg8qXwsxyfPLo4GChbGGRbRCuM3mB1lG1qHEivJ0vynsgol
p0t8jaXSFVBVgYj+C6Vd9/hlieUcOwIDAQABAoICAQDFitqh6TxqITlFBwv6vK9d
b696371XrFdo1F57RwcdxHnkklCUnWh/BcgIgJx6eUJV3nq2LibPgjQva6hF5NCc
89QDNNfBjMmgyRaqjsSKx5sm4U5Lus2R+UFzi4mEcpUI3m99XhGVKAUV8Fo4DLcl
3LlrMTVNXH3dbdj0va4NGcfwkZiWYJI+sPliYs24LtK/dADJJro/MqfQef7OTsWV
0kHHMSoXhzlC7fNvfd8VUFw0Ym99pC3iJclc155feWyk2FwDok7xjqFmR4KTrD1M
PLm/7+ooOFX5WdHVnULSZlb3HSJxCV7l1JJ7QXo/nKS/s59X875n8OWjdoc7lD4T
Xw/K9CzJCyhJ/HDhTAea1+MNTig4a6wjdSim6vasig/Gkot6jjS2lhnZae8ZhYxP
GUx4JcPthHppgHt8s6Jb2PHuqNVRmVB0x41c5mmXOnJcSqOX0XhbSbeS1TUV8BiC
HMaa+agt7RpQOb5uxpb+Hath/88tsjDXI0ZHNAG43ndkHxSQQ9P/q/m5uaLwuJyo
Yb06yUy/g7ceXpJFjGsjO+33DmamvligqOswgg+oazMFo8S9ZUJw6sSXhM/XiHla
JOj+Vatfj0ViVcaGlO2kWughuCT5thn92bgC9V2VnJhbaSzSaQlRphlbuSYJEYj0
S1uIbwPzTrcBQuekwY50YQKCAQEA/vve5K/nAnw4KLSrKwwCp9trYSm8C5czv2jV
tn6vQtckQMrw/hubX7TcTTgTuGboGdHMwZMFJBKpx6AlRHCR5IBw8fR1z+58c+2V
VJgllc23eKwCcBMKoe6LmsiUXOWmc7MuHc+qQS+9OemO93nNafsSwFCkucBFQs/3
Yx7J3zNvMOuy+dq3jrxO0xl2jBF0pcmJF/czrvbMCD7tvDntgqvpAnybgrwm2cu3
Q5F6i+E5w6VDhCprQL/aK95iT7cPmfdGxsUCdfNzDGIJFHZp2Hrar1TsOP6ESsDl
Q/Oz9oO1vMy7MymJjWFoVELBlCBxDEgubyM1f8cE1tQ6UAqFSwKCAQEAz8HnKWPe
NWZtqdAzSmY+3ZxSe1BbukOo4XtCV8LfRHGazKpXMTqsO9l7ynK7ifXv3b3GHTr+
ck2Af/vyiVx6f7Ty2dmBotFQDzg0HfKD2skAPyH8cHpA8TUeL3yMOR3XQU5/pOnG
tn84n7KWpAyZXh8gzMnmzWjMlb9pUlkKcATUj0gb8iSa9PV0zBwMKYKY0ngznJT2
CgE1vhy59rpuUVMrQ8i5iW9jbqYVrqID+ta2DWgcLsEXft7jKfupnRHF0Dvc650p
+Lkxv0YgKjUg5sYc2QJbIiBxXaW0cTRrw/KfOe4kvdG5RMF60Six+W1DIW2l+qi3
irnDRvRm1N6e0QKCAQEA86d5MaxJIl3TSEqEeikK7J3GuV0pHSZKQ7EI70+VaFiv
gt6qdReqXEU2cu+QIJjtV6bcc2lq8zKGXITSt9ieAO0fgIWqgpyQ/jJcjS6qU8D1
fnFYDwKTGXQaoTjkVPT6HvtsqP4E4i+dMZbWj/MrcAeEvpMRJZLuXE7gRi5ol0nO
CcBhEVKILvQQmrZtSqFvhvDTeTw2fg3FoGeJw2DTbheaHE84RzBGK774C7Abm0kI
asUkhEoInSH3eA4UgbobRXQ+hLhDhrSxDncr2ArjUALtr7eF11yWy9wR+OIK6Rio
9JXqmJQrphcbm9ECq+poPGVJQdgySjzCigrZAh1biwKCAQBiBnFVXCOaOov/lZa9
weRjl8BrIo1FI2tpiEjTM8U4fAm4C588QRzG2GTKLrxB6eKVU1dIr28i62J4AJ59
JT8/RldXZoL+GZiWtcQRZT3FWxVctGJxh51gsdleOnvG70eDLtCXNR5nOTu0TgU5
viAXAsTtG05lGM9+0GOXUR/VntHUEQfuhkr+zVmgfJNYeqA0njZr6PT134BGBTPR
MEGg6Yb+YpT4PbBCouaUESmjju8zAC5b+Qtm9y9jvbRXwez9xWEFYpBNJMROJX5D
q/GsMUmnMq9hOMGEmAy9ZSh7udxa7vwy++NYh5m1Wmgu8di8ywmHbVe8gs2aivKB
+dAhAoIBAQC7nSuRSmRGeKJCAqikMFPVdFdLaTnEw4i/fcyYsPa+o2hTWXyLll8K
lwSnBxe+BCvdeQ8cWzg3rPaIBVzUjDecZjdwjcHnhlHvgHjEvFX339rvpD7J2HIb
DaVqvtPniCFdNK4Jyvd3JMtNq34SIHFAcmB9358JuKsOwCmk8CpMAqKPVsKj7m6H
ETISh/K8aI2vZxVZ4WN4FsQTCqmtQDXFSGpZF5EZSpMJIB3ZZLt2jyyDW2DaZ+1T
yuVl9jU56fTtacQROQY7cvrwznX0lFpmniwl0Aj0wln/svFAqKo1+RujqApw5iYn
ssH1dH2tESx6RpMMyLYihjHVDC/ULUVu
-----END PRIVATE KEY-----

View File

@ -10,12 +10,7 @@ distinguished_name = dn
CN = example.com
[ req_v3 ]
subjectAltName = @alt_names
2.1.1.1=ASN1:UTF8String:A UTF8String Extension
2.1.1.2=ASN1:UTF8:A UTF8 Extension
2.1.1.3=ASN1:IA5:An IA5 Extension
2.1.1.4=ASN1:VISIBLE:A Visible Extension
[ alt_names ]
DNS.1 = example.com
IP.1 = 127.0.0.1

View File

@ -0,0 +1,18 @@
[ req ]
default_bits = 2048
encrypt_key = no
prompt = no
default_md = sha256
req_extensions = req_v3
distinguished_name = dn
[ dn ]
CN = example.com
[ req_v3 ]
subjectAltName = @alt_names
[ alt_names ]
DNS.1 = example.com
IP.1 = 127.0.0.1
URI.1 = spiffe://example.com/host

View File

@ -0,0 +1,17 @@
-----BEGIN CERTIFICATE REQUEST-----
MIICpTCCAY0CAQAwFjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQDEtoz6THzA8RFNJ+wu40Pa30Inyprv3xRGYA71
0T3yLrWUA0xaS8i7HHXDaEVmtHi7I+dFRqGwCgtDLY3sXN1C1t/U6V6xhhQ1hRW7
PJhbGfsfi8uBx83amWiSMlmEBYPryQzPS+8mmRErBi6EdmgbdGWV5IcovMddDxE1
Npc1vwmTxDUOe6mRSa8UkaR9nwFl8LTz9clIkGlOJLHWD2oX15PVr7SKYco+MrIh
HLKkYMgATFJ05EKLyRxO/lQWD6ibUYJuGhFeNyjk34swl3uoWQBGndxcs2BQP4OL
EfnsoXVDrHwjZ1FWSu/Bf6TfKvwo5It1IZLnm+cCTqxCnaLRAgMBAAGgSjBIBgkq
hkiG9w0BCQ4xOzA5MDcGA1UdEQQwMC6CC2V4YW1wbGUuY29thwR/AAABhhlzcGlm
ZmU6Ly9leGFtcGxlLmNvbS9ob3N0MA0GCSqGSIb3DQEBCwUAA4IBAQBw2y7bPrLk
B7DrZRvO/s8yj/Mi2iS/q3KEACEUxHTXH9GrqnQJ1n00WjaEu5JgXW8F08738nj/
QhO5IM9ZMBtFyt9/GguZzGWnGUGUvtfM/ps/qzF6lAnjxYnFfqJeDWhg4SQsW6ZW
eFZ3S1kx0iQjy+Y7oWZNObbgDhszdJa6swN1WJBB8BZuiDJYXMBzfWdR6aZStJ0Z
lUHyaQbILXRc+meuDY7KeILJhldlE8oU/NENO1w1WXcsseXg8790pPYg+uR/uXg0
0iWPtqgjO+55eAvkZ5nY0N/kABV1oaCB8bVs6/2HPqquPX6c+xkcUI/HY8SJgWzk
AHCG7VIB4W94
-----END CERTIFICATE REQUEST-----

View File

@ -0,0 +1,18 @@
-----BEGIN CERTIFICATE-----
MIIC6jCCAdKgAwIBAgIJAJIiPq+77hekMA0GCSqGSIb3DQEBBQUAMBYxFDASBgNV
BAMTC2V4YW1wbGUuY29tMB4XDTE4MDMzMTE2MTE0NVoXDTE5MDMzMTE2MTE0NVow
FjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
ggEKAoIBAQDEtoz6THzA8RFNJ+wu40Pa30Inyprv3xRGYA710T3yLrWUA0xaS8i7
HHXDaEVmtHi7I+dFRqGwCgtDLY3sXN1C1t/U6V6xhhQ1hRW7PJhbGfsfi8uBx83a
mWiSMlmEBYPryQzPS+8mmRErBi6EdmgbdGWV5IcovMddDxE1Npc1vwmTxDUOe6mR
Sa8UkaR9nwFl8LTz9clIkGlOJLHWD2oX15PVr7SKYco+MrIhHLKkYMgATFJ05EKL
yRxO/lQWD6ibUYJuGhFeNyjk34swl3uoWQBGndxcs2BQP4OLEfnsoXVDrHwjZ1FW
Su/Bf6TfKvwo5It1IZLnm+cCTqxCnaLRAgMBAAGjOzA5MDcGA1UdEQQwMC6CC2V4
YW1wbGUuY29thwR/AAABhhlzcGlmZmU6Ly9leGFtcGxlLmNvbS9ob3N0MA0GCSqG
SIb3DQEBBQUAA4IBAQDhR59hSpL4k4wbK3bA17YoNwFBsDpDcoU2iB9NDUTj+j+T
Rgumt+VHtgxuGRDFPQ+0D2hmJJHNCHKulgeDKVLtY/c5dCEsk8epLQwoCd/pQsNR
Lj102g83rCrU0pfTFjAUoecmHBFt7GDxVyWDsJgGItMatPQuWyZXTzO8JdhCfpMP
m7z65VYZjIPgevpSR5NVJDU8u2jRCkRQBFqOXotJS6EObu4P8aly4YhwiMf1B0L8
60XHbBksOQSZOky37uFhaab78bAu5nd2kN1K4qSObTJshCZAwRYk0XdCjDrMcZRJ
Fp+yygib+K8e7o71Co0zUdSU0yxOKGsWvjz1BUVl
-----END CERTIFICATE-----

View File

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDEtoz6THzA8RFN
J+wu40Pa30Inyprv3xRGYA710T3yLrWUA0xaS8i7HHXDaEVmtHi7I+dFRqGwCgtD
LY3sXN1C1t/U6V6xhhQ1hRW7PJhbGfsfi8uBx83amWiSMlmEBYPryQzPS+8mmREr
Bi6EdmgbdGWV5IcovMddDxE1Npc1vwmTxDUOe6mRSa8UkaR9nwFl8LTz9clIkGlO
JLHWD2oX15PVr7SKYco+MrIhHLKkYMgATFJ05EKLyRxO/lQWD6ibUYJuGhFeNyjk
34swl3uoWQBGndxcs2BQP4OLEfnsoXVDrHwjZ1FWSu/Bf6TfKvwo5It1IZLnm+cC
TqxCnaLRAgMBAAECggEAYLdYbR/6HmroFMVSLGN000H9ps7IirNlpoxIDrhH+rDY
eeN9QNAN62E8zUyRAsQsr+YhKUBm8sSdcPQO2W13JAu9lVMAScwgV4gNfTd3uSL3
AzWaYz63iYjvjyHOPUjw6Za6A5nUBWgwtrSdXmdRHF6IK8Bma7MVWj20OjOS+MsM
ScXk+yMTzpQYZ+AhP6rgcccn6djtk+Mqrpa7yW5cTDkQ0+/MF0KR7tYUbakRSimI
Ph6e+zFt4infOWP5fDr0oSpMXA2chh0INTtxbltnJzvaaPF8LSzyihWTZszABc84
Ckgrvmt5DViYbmfKHk0csS/xF/wdygfkkJHML8l/IQKBgQD9CMaDgfpM78uH8Kgm
Ja/ANu4Te5zO/n5E96PHdvCN+m7pCMgYuXuKgXDADgD1O6MItzDnEpkubluffARf
1eJyw9ner0tTAs8bZgtKdLQvaghq5Afk1+m8XDTskJsVLVGrozvJLuabPqnZrkRH
AxLdZjiAh6z2csFVYTQnMQSfhQKBgQDHBMjapcDx9y/jUq/yoFvwkdT3THQO9JgK
XC5NOHGVhyT3695wpqi/ANA4b8P9MmAzcUkT8a3jcqV87OIQmK3Y1oGvjHQCKS60
OYE9TadpxwW2uzxS5T7YegXf5L3uHinoWHlLklN+Q9pvJStw4QrDzhd8rtcZA+FN
KBmjzYdJ3QKBgQDYutl97qi7mXEVgPYlpoYA94u4OFq5mZYB8LLhuGiW03iINbNe
KhE9M12lwtjjNC+S2YYThgSaln/3/LuqcoLBlitY54B3G6LVbvQg1BE5w3JuS97P
Dnjvk3LpZXrQCr83altdGMUBGA1XnEJzKJjR9ipTPOLTPLuIK/gF0aCKGQKBgQCm
ZFitfZGge4M9Mt/KIcpciwCcNf5+ln8bglBv3XYRhykgYsLaOmyxLLPpy3/4DAsk
V1263//7PtofZUnoiE4pEcbhh7NiLx5OLhngsDD9Hhmn2kkoIWR2xyZsN6mYEP4G
tRnMVi2aTo6tCE2WlYBTjtZSNze9QWI4CQPO0MKAvQKBgQCzpJAJXl04zQv9S5uW
pH3xShmd0Zjv9tNyOVNqWUeg47IFzNC2w/6FqYkhd9C4DCAibzPx7WkVjYAR+ivY
NQv1usVhV3maJX5rw+C4Zck8kAmiqMbLacUVdy/5E2Mbk7xqjAvu+qrMFdSk/2GR
raR1xOEvE0cKWIwr8c8wIva4wA==
-----END PRIVATE KEY-----

View File

@ -2,16 +2,9 @@ package ldap
import (
"context"
"crypto/x509"
"encoding/pem"
"fmt"
"strings"
"text/template"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/vault/helper/consts"
"github.com/hashicorp/vault/helper/ldaputil"
"github.com/hashicorp/vault/helper/tlsutil"
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/logical/framework"
)
@ -19,105 +12,7 @@ import (
func pathConfig(b *backend) *framework.Path {
return &framework.Path{
Pattern: `config`,
Fields: map[string]*framework.FieldSchema{
"url": &framework.FieldSchema{
Type: framework.TypeString,
Default: "ldap://127.0.0.1",
Description: "LDAP URL to connect to (default: ldap://127.0.0.1). Multiple URLs can be specified by concatenating them with commas; they will be tried in-order.",
},
"userdn": &framework.FieldSchema{
Type: framework.TypeString,
Description: "LDAP domain to use for users (eg: ou=People,dc=example,dc=org)",
},
"binddn": &framework.FieldSchema{
Type: framework.TypeString,
Description: "LDAP DN for searching for the user DN (optional)",
},
"bindpass": &framework.FieldSchema{
Type: framework.TypeString,
Description: "LDAP password for searching for the user DN (optional)",
},
"groupdn": &framework.FieldSchema{
Type: framework.TypeString,
Description: "LDAP search base to use for group membership search (eg: ou=Groups,dc=example,dc=org)",
},
"groupfilter": &framework.FieldSchema{
Type: framework.TypeString,
Default: "(|(memberUid={{.Username}})(member={{.UserDN}})(uniqueMember={{.UserDN}}))",
Description: `Go template for querying group membership of user (optional)
The template can access the following context variables: UserDN, Username
Example: (&(objectClass=group)(member:1.2.840.113556.1.4.1941:={{.UserDN}}))
Default: (|(memberUid={{.Username}})(member={{.UserDN}})(uniqueMember={{.UserDN}}))`,
},
"groupattr": &framework.FieldSchema{
Type: framework.TypeString,
Default: "cn",
Description: `LDAP attribute to follow on objects returned by <groupfilter>
in order to enumerate user group membership.
Examples: "cn" or "memberOf", etc.
Default: cn`,
},
"upndomain": &framework.FieldSchema{
Type: framework.TypeString,
Description: "Enables userPrincipalDomain login with [username]@UPNDomain (optional)",
},
"userattr": &framework.FieldSchema{
Type: framework.TypeString,
Default: "cn",
Description: "Attribute used for users (default: cn)",
},
"certificate": &framework.FieldSchema{
Type: framework.TypeString,
Description: "CA certificate to use when verifying LDAP server certificate, must be x509 PEM encoded (optional)",
},
"discoverdn": &framework.FieldSchema{
Type: framework.TypeBool,
Description: "Use anonymous bind to discover the bind DN of a user (optional)",
},
"insecure_tls": &framework.FieldSchema{
Type: framework.TypeBool,
Description: "Skip LDAP server SSL Certificate verification - VERY insecure (optional)",
},
"starttls": &framework.FieldSchema{
Type: framework.TypeBool,
Description: "Issue a StartTLS command after establishing unencrypted connection (optional)",
},
"tls_min_version": &framework.FieldSchema{
Type: framework.TypeString,
Default: "tls12",
Description: "Minimum TLS version to use. Accepted values are 'tls10', 'tls11' or 'tls12'. Defaults to 'tls12'",
},
"tls_max_version": &framework.FieldSchema{
Type: framework.TypeString,
Default: "tls12",
Description: "Maximum TLS version to use. Accepted values are 'tls10', 'tls11' or 'tls12'. Defaults to 'tls12'",
},
"deny_null_bind": &framework.FieldSchema{
Type: framework.TypeBool,
Default: true,
Description: "Denies an unauthenticated LDAP bind request if the user's password is empty; defaults to true",
},
"case_sensitive_names": &framework.FieldSchema{
Type: framework.TypeBool,
Description: "If true, case sensitivity will be used when comparing usernames and groups for matching policies.",
},
},
Fields: ldaputil.ConfigFields(),
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.ReadOperation: b.pathConfigRead,
@ -140,7 +35,7 @@ func (b *backend) Config(ctx context.Context, req *logical.Request) (*ldaputil.C
}
// Create a new ConfigEntry, filling in defaults where appropriate
result, err := b.newConfigEntry(fd)
result, err := ldaputil.NewConfigEntry(fd)
if err != nil {
return nil, err
}
@ -195,147 +90,14 @@ func (b *backend) pathConfigRead(ctx context.Context, req *logical.Request, d *f
}
resp := &logical.Response{
Data: map[string]interface{}{
"url": cfg.Url,
"userdn": cfg.UserDN,
"groupdn": cfg.GroupDN,
"groupfilter": cfg.GroupFilter,
"groupattr": cfg.GroupAttr,
"upndomain": cfg.UPNDomain,
"userattr": cfg.UserAttr,
"certificate": cfg.Certificate,
"insecure_tls": cfg.InsecureTLS,
"starttls": cfg.StartTLS,
"binddn": cfg.BindDN,
"deny_null_bind": cfg.DenyNullBind,
"discoverdn": cfg.DiscoverDN,
"tls_min_version": cfg.TLSMinVersion,
"tls_max_version": cfg.TLSMaxVersion,
"case_sensitive_names": *cfg.CaseSensitiveNames,
},
Data: cfg.PasswordlessMap(),
}
return resp, nil
}
/*
* Creates and initializes a ConfigEntry object with its default values,
* as specified by the passed schema.
*/
func (b *backend) newConfigEntry(d *framework.FieldData) (*ldaputil.ConfigEntry, error) {
cfg := new(ldaputil.ConfigEntry)
url := d.Get("url").(string)
if url != "" {
cfg.Url = strings.ToLower(url)
}
userattr := d.Get("userattr").(string)
if userattr != "" {
cfg.UserAttr = strings.ToLower(userattr)
}
userdn := d.Get("userdn").(string)
if userdn != "" {
cfg.UserDN = userdn
}
groupdn := d.Get("groupdn").(string)
if groupdn != "" {
cfg.GroupDN = groupdn
}
groupfilter := d.Get("groupfilter").(string)
if groupfilter != "" {
// Validate the template before proceeding
_, err := template.New("queryTemplate").Parse(groupfilter)
if err != nil {
return nil, errwrap.Wrapf("invalid groupfilter: {{err}}", err)
}
cfg.GroupFilter = groupfilter
}
groupattr := d.Get("groupattr").(string)
if groupattr != "" {
cfg.GroupAttr = groupattr
}
upndomain := d.Get("upndomain").(string)
if upndomain != "" {
cfg.UPNDomain = upndomain
}
certificate := d.Get("certificate").(string)
if certificate != "" {
block, _ := pem.Decode([]byte(certificate))
if block == nil || block.Type != "CERTIFICATE" {
return nil, fmt.Errorf("failed to decode PEM block in the certificate")
}
_, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, errwrap.Wrapf("failed to parse certificate: {{err}}", err)
}
cfg.Certificate = certificate
}
insecureTLS := d.Get("insecure_tls").(bool)
if insecureTLS {
cfg.InsecureTLS = insecureTLS
}
cfg.TLSMinVersion = d.Get("tls_min_version").(string)
if cfg.TLSMinVersion == "" {
return nil, fmt.Errorf("failed to get 'tls_min_version' value")
}
var ok bool
_, ok = tlsutil.TLSLookup[cfg.TLSMinVersion]
if !ok {
return nil, fmt.Errorf("invalid 'tls_min_version'")
}
cfg.TLSMaxVersion = d.Get("tls_max_version").(string)
if cfg.TLSMaxVersion == "" {
return nil, fmt.Errorf("failed to get 'tls_max_version' value")
}
_, ok = tlsutil.TLSLookup[cfg.TLSMaxVersion]
if !ok {
return nil, fmt.Errorf("invalid 'tls_max_version'")
}
if cfg.TLSMaxVersion < cfg.TLSMinVersion {
return nil, fmt.Errorf("'tls_max_version' must be greater than or equal to 'tls_min_version'")
}
startTLS := d.Get("starttls").(bool)
if startTLS {
cfg.StartTLS = startTLS
}
bindDN := d.Get("binddn").(string)
if bindDN != "" {
cfg.BindDN = bindDN
}
bindPass := d.Get("bindpass").(string)
if bindPass != "" {
cfg.BindPassword = bindPass
}
denyNullBind := d.Get("deny_null_bind").(bool)
if denyNullBind {
cfg.DenyNullBind = denyNullBind
}
discoverDN := d.Get("discoverdn").(bool)
if discoverDN {
cfg.DiscoverDN = discoverDN
}
caseSensitiveNames, ok := d.GetOk("case_sensitive_names")
if ok {
cfg.CaseSensitiveNames = new(bool)
*cfg.CaseSensitiveNames = caseSensitiveNames.(bool)
}
return cfg, nil
}
func (b *backend) pathConfigWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
// Build a ConfigEntry struct out of the supplied FieldData
cfg, err := b.newConfigEntry(d)
cfg, err := ldaputil.NewConfigEntry(d)
if err != nil {
return logical.ErrorResponse(err.Error()), nil
}

View File

@ -66,10 +66,12 @@ func (c *AuthCommand) Run(args []string) int {
for _, arg := range args {
switch {
case strings.HasPrefix(arg, "-methods"):
c.UI.Warn(wrapAtLength(
"WARNING! The -methods flag is deprecated. Please use "+
"\"vault auth list\" instead. This flag will be removed in "+
"Vault 0.11 (or later).") + "\n")
if Format(c.UI) == "table" {
c.UI.Warn(wrapAtLength(
"WARNING! The -methods flag is deprecated. Please use "+
"\"vault auth list\" instead. This flag will be removed in "+
"Vault 0.11 (or later).") + "\n")
}
return (&AuthListCommand{
BaseCommand: &BaseCommand{
UI: c.UI,
@ -77,10 +79,12 @@ func (c *AuthCommand) Run(args []string) int {
},
}).Run(nil)
case strings.HasPrefix(arg, "-method-help"):
c.UI.Warn(wrapAtLength(
"WARNING! The -method-help flag is deprecated. Please use "+
"\"vault auth help\" instead. This flag will be removed in "+
"Vault 0.11 (or later).") + "\n")
if Format(c.UI) == "table" {
c.UI.Warn(wrapAtLength(
"WARNING! The -method-help flag is deprecated. Please use "+
"\"vault auth help\" instead. This flag will be removed in "+
"Vault 0.11 (or later).") + "\n")
}
// Parse the args to pull out the method, suppressing any errors because
// there could be other flags that we don't care about.
f := flag.NewFlagSet("", flag.ContinueOnError)
@ -101,11 +105,13 @@ func (c *AuthCommand) Run(args []string) int {
// If we got this far, we have an arg or a series of args that should be
// passed directly to the new "vault login" command.
c.UI.Warn(wrapAtLength(
"WARNING! The \"vault auth ARG\" command is deprecated and is now a "+
"subcommand for interacting with auth methods. To authenticate "+
"locally to Vault, use \"vault login\" instead. This backwards "+
"compatibility will be removed in Vault 0.11 (or later).") + "\n")
if Format(c.UI) == "table" {
c.UI.Warn(wrapAtLength(
"WARNING! The \"vault auth ARG\" command is deprecated and is now a "+
"subcommand for interacting with auth methods. To authenticate "+
"locally to Vault, use \"vault login\" instead. This backwards "+
"compatibility will be removed in Vault 0.11 (or later).") + "\n")
}
return (&LoginCommand{
BaseCommand: &BaseCommand{
UI: c.UI,

View File

@ -284,7 +284,7 @@ func (c *BaseCommand) flagSet(bit FlagSetBit) *FlagSets {
Usage: "Print only the field with the given name. Specifying " +
"this option will take precedence over other formatting " +
"directives. The result will not have a trailing newline " +
"making it idea for piping to other processes.",
"making it ideal for piping to other processes.",
})
}

View File

@ -6,6 +6,7 @@ import (
"os/signal"
"syscall"
ad "github.com/hashicorp/vault-plugin-secrets-ad/plugin"
gcp "github.com/hashicorp/vault-plugin-secrets-gcp/plugin"
kv "github.com/hashicorp/vault-plugin-secrets-kv"
"github.com/hashicorp/vault/audit"
@ -110,6 +111,7 @@ var (
}
logicalBackends = map[string]logical.Factory{
"ad": ad.Factory,
"aws": aws.Factory,
"cassandra": cassandra.Factory,
"consul": consul.Factory,
@ -211,359 +213,212 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
},
}
getBaseCommand := func() *BaseCommand {
return &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
client: runOpts.Client,
}
}
Commands = map[string]cli.CommandFactory{
"audit": func() (cli.Command, error) {
return &AuditCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
}, nil
},
"audit disable": func() (cli.Command, error) {
return &AuditDisableCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
}, nil
},
"audit enable": func() (cli.Command, error) {
return &AuditEnableCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
}, nil
},
"audit list": func() (cli.Command, error) {
return &AuditListCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
}, nil
},
"auth tune": func() (cli.Command, error) {
return &AuthTuneCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
}, nil
},
"auth": func() (cli.Command, error) {
return &AuthCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
Handlers: loginHandlers,
BaseCommand: getBaseCommand(),
Handlers: loginHandlers,
}, nil
},
"auth disable": func() (cli.Command, error) {
return &AuthDisableCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
}, nil
},
"auth enable": func() (cli.Command, error) {
return &AuthEnableCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
}, nil
},
"auth help": func() (cli.Command, error) {
return &AuthHelpCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
Handlers: loginHandlers,
BaseCommand: getBaseCommand(),
Handlers: loginHandlers,
}, nil
},
"auth list": func() (cli.Command, error) {
return &AuthListCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
}, nil
},
"delete": func() (cli.Command, error) {
return &DeleteCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
}, nil
},
"lease": func() (cli.Command, error) {
return &LeaseCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
}, nil
},
"lease renew": func() (cli.Command, error) {
return &LeaseRenewCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
}, nil
},
"lease revoke": func() (cli.Command, error) {
return &LeaseRevokeCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
}, nil
},
"list": func() (cli.Command, error) {
return &ListCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
}, nil
},
"login": func() (cli.Command, error) {
return &LoginCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
Handlers: loginHandlers,
BaseCommand: getBaseCommand(),
Handlers: loginHandlers,
}, nil
},
"operator": func() (cli.Command, error) {
return &OperatorCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
}, nil
},
"operator generate-root": func() (cli.Command, error) {
return &OperatorGenerateRootCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
}, nil
},
"operator init": func() (cli.Command, error) {
return &OperatorInitCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
}, nil
},
"operator key-status": func() (cli.Command, error) {
return &OperatorKeyStatusCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
}, nil
},
"operator rekey": func() (cli.Command, error) {
return &OperatorRekeyCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
}, nil
},
"operator rotate": func() (cli.Command, error) {
return &OperatorRotateCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
}, nil
},
"operator seal": func() (cli.Command, error) {
return &OperatorSealCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
}, nil
},
"operator step-down": func() (cli.Command, error) {
return &OperatorStepDownCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
}, nil
},
"operator unseal": func() (cli.Command, error) {
return &OperatorUnsealCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
}, nil
},
"path-help": func() (cli.Command, error) {
return &PathHelpCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
}, nil
},
"policy": func() (cli.Command, error) {
return &PolicyCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
}, nil
},
"policy delete": func() (cli.Command, error) {
return &PolicyDeleteCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
}, nil
},
"policy fmt": func() (cli.Command, error) {
return &PolicyFmtCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
}, nil
},
"policy list": func() (cli.Command, error) {
return &PolicyListCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
}, nil
},
"policy read": func() (cli.Command, error) {
return &PolicyReadCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
}, nil
},
"policy write": func() (cli.Command, error) {
return &PolicyWriteCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
}, nil
},
"read": func() (cli.Command, error) {
return &ReadCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
}, nil
},
"secrets": func() (cli.Command, error) {
return &SecretsCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
}, nil
},
"secrets disable": func() (cli.Command, error) {
return &SecretsDisableCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
}, nil
},
"secrets enable": func() (cli.Command, error) {
return &SecretsEnableCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
}, nil
},
"secrets list": func() (cli.Command, error) {
return &SecretsListCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
}, nil
},
"secrets move": func() (cli.Command, error) {
return &SecretsMoveCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
}, nil
},
"secrets tune": func() (cli.Command, error) {
return &SecretsTuneCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
}, nil
},
"server": func() (cli.Command, error) {
@ -583,193 +438,123 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
},
"ssh": func() (cli.Command, error) {
return &SSHCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
}, nil
},
"status": func() (cli.Command, error) {
return &StatusCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
}, nil
},
"token": func() (cli.Command, error) {
return &TokenCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
}, nil
},
"token create": func() (cli.Command, error) {
return &TokenCreateCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
}, nil
},
"token capabilities": func() (cli.Command, error) {
return &TokenCapabilitiesCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
}, nil
},
"token lookup": func() (cli.Command, error) {
return &TokenLookupCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
}, nil
},
"token renew": func() (cli.Command, error) {
return &TokenRenewCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
}, nil
},
"token revoke": func() (cli.Command, error) {
return &TokenRevokeCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
}, nil
},
"unwrap": func() (cli.Command, error) {
return &UnwrapCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
}, nil
},
"version": func() (cli.Command, error) {
return &VersionCommand{
VersionInfo: version.GetVersion(),
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
}, nil
},
"write": func() (cli.Command, error) {
return &WriteCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
}, nil
},
"kv": func() (cli.Command, error) {
return &KVCommand{
BaseCommand: &BaseCommand{
UI: ui,
},
BaseCommand: getBaseCommand(),
}, nil
},
"kv put": func() (cli.Command, error) {
return &KVPutCommand{
BaseCommand: &BaseCommand{
UI: ui,
},
BaseCommand: getBaseCommand(),
}, nil
},
"kv patch": func() (cli.Command, error) {
return &KVPatchCommand{
BaseCommand: &BaseCommand{
UI: ui,
},
BaseCommand: getBaseCommand(),
}, nil
},
"kv get": func() (cli.Command, error) {
return &KVGetCommand{
BaseCommand: &BaseCommand{
UI: ui,
},
BaseCommand: getBaseCommand(),
}, nil
},
"kv delete": func() (cli.Command, error) {
return &KVDeleteCommand{
BaseCommand: &BaseCommand{
UI: ui,
},
BaseCommand: getBaseCommand(),
}, nil
},
"kv list": func() (cli.Command, error) {
return &KVListCommand{
BaseCommand: &BaseCommand{
UI: ui,
},
BaseCommand: getBaseCommand(),
}, nil
},
"kv destroy": func() (cli.Command, error) {
return &KVDestroyCommand{
BaseCommand: &BaseCommand{
UI: ui,
},
BaseCommand: getBaseCommand(),
}, nil
},
"kv undelete": func() (cli.Command, error) {
return &KVUndeleteCommand{
BaseCommand: &BaseCommand{
UI: ui,
},
BaseCommand: getBaseCommand(),
}, nil
},
"kv enable-versioning": func() (cli.Command, error) {
return &KVEnableVersioningCommand{
BaseCommand: &BaseCommand{
UI: ui,
},
BaseCommand: getBaseCommand(),
}, nil
},
"kv metadata": func() (cli.Command, error) {
return &KVMetadataCommand{
BaseCommand: &BaseCommand{
UI: ui,
},
BaseCommand: getBaseCommand(),
}, nil
},
"kv metadata put": func() (cli.Command, error) {
return &KVMetadataPutCommand{
BaseCommand: &BaseCommand{
UI: ui,
},
BaseCommand: getBaseCommand(),
}, nil
},
"kv metadata get": func() (cli.Command, error) {
return &KVMetadataGetCommand{
BaseCommand: &BaseCommand{
UI: ui,
},
BaseCommand: getBaseCommand(),
}, nil
},
"kv metadata delete": func() (cli.Command, error) {
return &KVMetadataDeleteCommand{
BaseCommand: &BaseCommand{
UI: ui,
},
BaseCommand: getBaseCommand(),
}, nil
},
}
@ -782,12 +567,9 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
return &DeprecatedCommand{
Old: "audit-disable",
New: "audit disable",
UI: ui,
Command: &AuditDisableCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
},
}, nil
},
@ -798,11 +580,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "audit enable",
UI: ui,
Command: &AuditEnableCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
},
}, nil
},
@ -813,11 +591,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "audit list",
UI: ui,
Command: &AuditListCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
},
}, nil
},
@ -828,11 +602,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "auth disable",
UI: ui,
Command: &AuthDisableCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
},
}, nil
},
@ -843,11 +613,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "auth enable",
UI: ui,
Command: &AuthEnableCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
},
}, nil
},
@ -858,11 +624,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "token capabilities",
UI: ui,
Command: &TokenCapabilitiesCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
},
}, nil
},
@ -873,11 +635,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "operator generate-root",
UI: ui,
Command: &OperatorGenerateRootCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
},
}, nil
},
@ -888,11 +646,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "operator init",
UI: ui,
Command: &OperatorInitCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
},
}, nil
},
@ -903,11 +657,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "operator key-status",
UI: ui,
Command: &OperatorKeyStatusCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
},
}, nil
},
@ -918,11 +668,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "lease renew",
UI: ui,
Command: &LeaseRenewCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
},
}, nil
},
@ -933,11 +679,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "lease revoke",
UI: ui,
Command: &LeaseRevokeCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
},
}, nil
},
@ -948,11 +690,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "secrets enable",
UI: ui,
Command: &SecretsEnableCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
},
}, nil
},
@ -963,11 +701,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "secrets tune",
UI: ui,
Command: &SecretsTuneCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
},
}, nil
},
@ -978,11 +712,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "secrets list",
UI: ui,
Command: &SecretsListCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
},
}, nil
},
@ -993,11 +723,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "policy read\" or \"vault policy list", // lol
UI: ui,
Command: &PoliciesDeprecatedCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
},
}, nil
},
@ -1008,11 +734,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "policy delete",
UI: ui,
Command: &PolicyDeleteCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
},
}, nil
},
@ -1023,11 +745,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "policy write",
UI: ui,
Command: &PolicyWriteCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
},
}, nil
},
@ -1038,11 +756,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "operator rekey",
UI: ui,
Command: &OperatorRekeyCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
},
}, nil
},
@ -1053,11 +767,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "secrets move",
UI: ui,
Command: &SecretsMoveCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
},
}, nil
},
@ -1068,11 +778,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "operator rotate",
UI: ui,
Command: &OperatorRotateCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
},
}, nil
},
@ -1083,11 +789,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "operator seal",
UI: ui,
Command: &OperatorSealCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
},
}, nil
},
@ -1098,11 +800,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "operator step-down",
UI: ui,
Command: &OperatorStepDownCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
},
}, nil
},
@ -1113,11 +811,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "token create",
UI: ui,
Command: &TokenCreateCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
},
}, nil
},
@ -1128,11 +822,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "token lookup",
UI: ui,
Command: &TokenLookupCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
},
}, nil
},
@ -1143,11 +833,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "token renew",
UI: ui,
Command: &TokenRenewCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
},
}, nil
},
@ -1158,11 +844,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "token revoke",
UI: ui,
Command: &TokenRevokeCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
},
}, nil
},
@ -1173,11 +855,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "secrets disable",
UI: ui,
Command: &SecretsDisableCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
},
}, nil
},
@ -1188,11 +866,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "operator unseal",
UI: ui,
Command: &OperatorUnsealCommand{
BaseCommand: &BaseCommand{
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
BaseCommand: getBaseCommand(),
},
}, nil
},

View File

@ -69,20 +69,18 @@ var Formatters = map[string]Formatter{
"yml": YamlFormatter{},
}
func format() string {
format := os.Getenv(EnvVaultFormat)
if format == "" {
format = "table"
}
return format
}
func Format(ui cli.Ui) string {
switch ui.(type) {
case *VaultUI:
return ui.(*VaultUI).format
}
return format()
format := os.Getenv(EnvVaultFormat)
if format == "" {
format = "table"
}
return format
}
// An output formatter for json output of an object

View File

@ -1,6 +1,7 @@
package command
import (
"bytes"
"os"
"strings"
"testing"
@ -91,13 +92,13 @@ func Test_Format_Parsing(t *testing.T) {
}{
{
"format",
[]string{"-format", "json"},
[]string{"token", "renew", "-format", "json"},
"{",
0,
},
{
"format_bad",
[]string{"-format", "nope-not-real"},
[]string{"token", "renew", "-format", "nope-not-real"},
"Invalid output format",
1,
},
@ -110,21 +111,24 @@ func Test_Format_Parsing(t *testing.T) {
client, closer := testVaultServer(t)
defer closer()
stdout := bytes.NewBuffer(nil)
stderr := bytes.NewBuffer(nil)
runOpts := &RunOptions{
Stdout: stdout,
Stderr: stderr,
Client: client,
}
// Login with the token so we can renew-self.
token, _ := testTokenAndAccessor(t, client)
client.SetToken(token)
ui, cmd := testTokenRenewCommand(t)
cmd.client = client
tc.args = setupEnv(tc.args)
code := cmd.Run(tc.args)
code := RunCustom(tc.args, runOpts)
if code != tc.code {
t.Errorf("expected %d to be %d", code, tc.code)
}
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
combined := stdout.String() + stderr.String()
if !strings.Contains(combined, tc.out) {
t.Errorf("expected %q to contain %q", combined, tc.out)
}

View File

@ -94,11 +94,12 @@ func (c *LeaseRenewCommand) Run(args []string) int {
case 2:
// Deprecation
// TODO: remove in 0.9.0
c.UI.Warn(wrapAtLength(
"WARNING! Specifying INCREMENT as a second argument is deprecated. " +
"Please use -increment instead. This will be removed in the next " +
"major release of Vault."))
if Format(c.UI) == "table" {
c.UI.Warn(wrapAtLength(
"WARNING! Specifying INCREMENT as a second argument is deprecated. " +
"Please use -increment instead. This will be removed in the next " +
"major release of Vault."))
}
leaseID = strings.TrimSpace(args[0])
parsed, err := time.ParseDuration(appendDurationSuffix(args[1]))
if err != nil {

View File

@ -167,26 +167,28 @@ func (c *LoginCommand) Run(args []string) int {
// TODO: remove in 0.10.0
switch {
case c.flagNoVerify:
c.UI.Warn(wrapAtLength(
"WARNING! The -no-verify flag is deprecated. In the past, Vault " +
"performed a lookup on a token after authentication. This is no " +
"longer the case for all auth methods except \"token\". Vault will " +
"still attempt to perform a lookup when given a token directly " +
"because that is how it gets the list of policies, ttl, and other " +
"metadata. To disable this lookup, specify \"lookup=false\" as a " +
"configuration option to the token auth method, like this:"))
c.UI.Warn("")
c.UI.Warn(" $ vault auth token=ABCD lookup=false")
c.UI.Warn("")
c.UI.Warn("Or omit the token and Vault will prompt for it:")
c.UI.Warn("")
c.UI.Warn(" $ vault auth lookup=false")
c.UI.Warn(" Token (will be hidden): ...")
c.UI.Warn("")
c.UI.Warn(wrapAtLength(
"If you are not using token authentication, you can safely omit this " +
"flag. Vault will not perform a lookup after authentication."))
c.UI.Warn("")
if Format(c.UI) == "table" {
c.UI.Warn(wrapAtLength(
"WARNING! The -no-verify flag is deprecated. In the past, Vault " +
"performed a lookup on a token after authentication. This is no " +
"longer the case for all auth methods except \"token\". Vault will " +
"still attempt to perform a lookup when given a token directly " +
"because that is how it gets the list of policies, ttl, and other " +
"metadata. To disable this lookup, specify \"lookup=false\" as a " +
"configuration option to the token auth method, like this:"))
c.UI.Warn("")
c.UI.Warn(" $ vault auth token=ABCD lookup=false")
c.UI.Warn("")
c.UI.Warn("Or omit the token and Vault will prompt for it:")
c.UI.Warn("")
c.UI.Warn(" $ vault auth lookup=false")
c.UI.Warn(" Token (will be hidden): ...")
c.UI.Warn("")
c.UI.Warn(wrapAtLength(
"If you are not using token authentication, you can safely omit this " +
"flag. Vault will not perform a lookup after authentication."))
c.UI.Warn("")
}
// There's no point in passing this to other auth handlers...
if c.flagMethod == "token" {

View File

@ -10,6 +10,7 @@ import (
"text/tabwriter"
"github.com/fatih/color"
"github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/command/token"
colorable "github.com/mattn/go-colorable"
"github.com/mitchellh/cli"
@ -22,8 +23,7 @@ type VaultUI struct {
// setupEnv parses args and may replace them and sets some env vars to known
// values based on format options
func setupEnv(args []string) []string {
var format string
func setupEnv(args []string) (retArgs []string, format string) {
var nextArgFormat bool
for _, arg := range args {
@ -65,10 +65,8 @@ func setupEnv(args []string) []string {
if format == "" {
format = "table"
}
// Put back into the env for later
os.Setenv(EnvVaultFormat, format)
return args
return args, format
}
type RunOptions struct {
@ -76,6 +74,7 @@ type RunOptions struct {
Stdout io.Writer
Stderr io.Writer
Address string
Client *api.Client
}
func Run(args []string) int {
@ -89,7 +88,8 @@ func RunCustom(args []string, runOpts *RunOptions) int {
runOpts = &RunOptions{}
}
args = setupEnv(args)
var format string
args, format = setupEnv(args)
// Don't use color if disabled
useColor := true
@ -104,8 +104,6 @@ func RunCustom(args []string, runOpts *RunOptions) int {
runOpts.Stderr = os.Stderr
}
format := format()
// Only use colored UI if stdout is a tty, and not disabled
if useColor && format == "table" {
if f, ok := runOpts.Stdout.(*os.File); ok {

View File

@ -217,9 +217,11 @@ func (c *OperatorGenerateRootCommand) Run(args []string) int {
// TODO: remove in 0.9.0
switch {
case c.flagGenOTP:
c.UI.Warn(wrapAtLength(
"The -gen-otp flag is deprecated. Please use the -generate-otp flag " +
"instead."))
if Format(c.UI) == "table" {
c.UI.Warn(wrapAtLength(
"WARNING! The -gen-otp flag is deprecated. Please use the -generate-otp flag " +
"instead."))
}
c.flagGenerateOTP = c.flagGenOTP
}

View File

@ -244,14 +244,18 @@ func (c *OperatorInitCommand) Run(args []string) int {
// Deprecations
// TODO: remove in 0.9.0
if c.flagAuto {
c.UI.Warn(wrapAtLength("WARNING! -auto is deprecated. Please use " +
"-consul-auto instead. This will be removed in Vault 0.11 " +
"(or later)."))
if Format(c.UI) == "table" {
c.UI.Warn(wrapAtLength("WARNING! -auto is deprecated. Please use " +
"-consul-auto instead. This will be removed in Vault 0.11 " +
"(or later)."))
}
c.flagConsulAuto = true
}
if c.flagCheck {
c.UI.Warn(wrapAtLength("WARNING! -check is deprecated. Please use " +
"-status instead. This will be removed in Vault 0.11 (or later)."))
if Format(c.UI) == "table" {
c.UI.Warn(wrapAtLength("WARNING! -check is deprecated. Please use " +
"-status instead. This will be removed in Vault 0.11 (or later)."))
}
c.flagStatus = true
}

View File

@ -269,21 +269,27 @@ func (c *OperatorRekeyCommand) Run(args []string) int {
// Deprecations
// TODO: remove in 0.9.0
if c.flagDelete {
c.UI.Warn(wrapAtLength(
"WARNING! The -delete flag is deprecated. Please use -backup-delete " +
"instead. This flag will be removed in Vault 0.11 (or later)."))
if Format(c.UI) == "table" {
c.UI.Warn(wrapAtLength(
"WARNING! The -delete flag is deprecated. Please use -backup-delete " +
"instead. This flag will be removed in Vault 0.11 (or later)."))
}
c.flagBackupDelete = true
}
if c.flagRetrieve {
c.UI.Warn(wrapAtLength(
"WARNING! The -retrieve flag is deprecated. Please use -backup-retrieve " +
"instead. This flag will be removed in Vault 0.11 (or later)."))
if Format(c.UI) == "table" {
c.UI.Warn(wrapAtLength(
"WARNING! The -retrieve flag is deprecated. Please use -backup-retrieve " +
"instead. This flag will be removed in Vault 0.11 (or later)."))
}
c.flagBackupRetrieve = true
}
if c.flagRecoveryKey {
c.UI.Warn(wrapAtLength(
"WARNING! The -recovery-key flag is deprecated. Please use -target=recovery " +
"instead. This flag will be removed in Vault 0.11 (or later)."))
if Format(c.UI) == "table" {
c.UI.Warn(wrapAtLength(
"WARNING! The -recovery-key flag is deprecated. Please use -target=recovery " +
"instead. This flag will be removed in Vault 0.11 (or later)."))
}
c.flagTarget = "recovery"
}
@ -344,25 +350,29 @@ func (c *OperatorRekeyCommand) init(client *api.Client) int {
// Print warnings about recovery, etc.
if len(c.flagPGPKeys) == 0 {
c.UI.Warn(wrapAtLength(
"WARNING! If you lose the keys after they are returned, there is no " +
"recovery. Consider canceling this operation and re-initializing " +
"with the -pgp-keys flag to protect the returned unseal keys along " +
"with -backup to allow recovery of the encrypted keys in case of " +
"emergency. You can delete the stored keys later using the -delete " +
"flag."))
c.UI.Output("")
if Format(c.UI) == "table" {
c.UI.Warn(wrapAtLength(
"WARNING! If you lose the keys after they are returned, there is no " +
"recovery. Consider canceling this operation and re-initializing " +
"with the -pgp-keys flag to protect the returned unseal keys along " +
"with -backup to allow recovery of the encrypted keys in case of " +
"emergency. You can delete the stored keys later using the -delete " +
"flag."))
c.UI.Output("")
}
}
if len(c.flagPGPKeys) > 0 && !c.flagBackup {
c.UI.Warn(wrapAtLength(
"WARNING! You are using PGP keys for encrypted the resulting unseal " +
"keys, but you did not enable the option to backup the keys to " +
"Vault's core. If you lose the encrypted keys after they are " +
"returned, you will not be able to recover them. Consider canceling " +
"this operation and re-running with -backup to allow recovery of the " +
"encrypted unseal keys in case of emergency. You can delete the " +
"stored keys later using the -delete flag."))
c.UI.Output("")
if Format(c.UI) == "table" {
c.UI.Warn(wrapAtLength(
"WARNING! You are using PGP keys for encrypted the resulting unseal " +
"keys, but you did not enable the option to backup the keys to " +
"Vault's core. If you lose the encrypted keys after they are " +
"returned, you will not be able to recover them. Consider canceling " +
"this operation and re-running with -backup to allow recovery of the " +
"encrypted unseal keys in case of emergency. You can delete the " +
"stored keys later using the -delete flag."))
c.UI.Output("")
}
}
// Provide the current status

View File

@ -1,6 +1,7 @@
package command
import (
"bytes"
"encoding/json"
"io/ioutil"
"os"
@ -147,7 +148,6 @@ func TestOperatorUnsealCommand_Run(t *testing.T) {
func TestOperatorUnsealCommand_Format(t *testing.T) {
defer func() {
os.Setenv(EnvVaultFormat, "")
os.Setenv(EnvVaultCLINoColor, "")
}()
@ -159,21 +159,28 @@ func TestOperatorUnsealCommand_Format(t *testing.T) {
t.Fatal(err)
}
ui, cmd := testOperatorUnsealCommand(t)
cmd.client = client
cmd.testOutput = ioutil.Discard
args := setupEnv([]string{"-format", "json"})
// Unseal with one key
code := cmd.Run(append(args, []string{
keys[0],
}...))
if exp := 0; code != exp {
t.Errorf("expected %d to be %d: %s", code, exp, ui.ErrorWriter.String())
stdout := bytes.NewBuffer(nil)
stderr := bytes.NewBuffer(nil)
runOpts := &RunOptions{
Stdout: stdout,
Stderr: stderr,
Client: client,
}
if !json.Valid(ui.OutputWriter.Bytes()) {
args, format := setupEnv([]string{"unseal", "-format", "json"})
if format != "json" {
t.Fatalf("expected %q, got %q", "json", format)
}
// Unseal with one key
code := RunCustom(append(args, []string{
keys[0],
}...), runOpts)
if exp := 0; code != exp {
t.Errorf("expected %d to be %d: %s", code, exp, stderr.String())
}
if !json.Valid(stdout.Bytes()) {
t.Error("expected output to be valid JSON")
}
}

View File

@ -207,8 +207,10 @@ func (c *TokenCreateCommand) Run(args []string) int {
// TODO: remove in 0.9.0
if c.flagLease != 0 {
c.UI.Warn("The -lease flag is deprecated. Please use -ttl instead.")
c.flagTTL = c.flagLease
if Format(c.UI) == "table" {
c.UI.Warn("The -lease flag is deprecated. Please use -ttl instead.")
c.flagTTL = c.flagLease
}
}
client, err := c.Client()

View File

@ -99,9 +99,10 @@ func (c *TokenRenewCommand) Run(args []string) int {
token = strings.TrimSpace(args[0])
case 2:
// TODO: remove in 0.9.0 - backwards compat
c.UI.Warn("Specifying increment as a second argument is deprecated. " +
"Please use -increment instead.")
if Format(c.UI) == "table" {
c.UI.Warn("Specifying increment as a second argument is deprecated. " +
"Please use -increment instead.")
}
token = strings.TrimSpace(args[0])
parsed, err := time.ParseDuration(appendDurationSuffix(args[1]))
if err != nil {

View File

@ -5,10 +5,235 @@ import (
"encoding/pem"
"errors"
"fmt"
"strings"
"text/template"
"github.com/hashicorp/vault/helper/tlsutil"
"github.com/hashicorp/vault/logical/framework"
"github.com/hashicorp/errwrap"
)
// ConfigFields returns all the config fields that can potentially be used by the LDAP client.
// Not all fields will be used by every integration.
func ConfigFields() map[string]*framework.FieldSchema {
return map[string]*framework.FieldSchema{
"url": {
Type: framework.TypeString,
Default: "ldap://127.0.0.1",
Description: "LDAP URL to connect to (default: ldap://127.0.0.1). Multiple URLs can be specified by concatenating them with commas; they will be tried in-order.",
},
"userdn": {
Type: framework.TypeString,
Description: "LDAP domain to use for users (eg: ou=People,dc=example,dc=org)",
},
"binddn": {
Type: framework.TypeString,
Description: "LDAP DN for searching for the user DN (optional)",
},
"bindpass": {
Type: framework.TypeString,
Description: "LDAP password for searching for the user DN (optional)",
},
"groupdn": {
Type: framework.TypeString,
Description: "LDAP search base to use for group membership search (eg: ou=Groups,dc=example,dc=org)",
},
"groupfilter": {
Type: framework.TypeString,
Default: "(|(memberUid={{.Username}})(member={{.UserDN}})(uniqueMember={{.UserDN}}))",
Description: `Go template for querying group membership of user (optional)
The template can access the following context variables: UserDN, Username
Example: (&(objectClass=group)(member:1.2.840.113556.1.4.1941:={{.UserDN}}))
Default: (|(memberUid={{.Username}})(member={{.UserDN}})(uniqueMember={{.UserDN}}))`,
},
"groupattr": {
Type: framework.TypeString,
Default: "cn",
Description: `LDAP attribute to follow on objects returned by <groupfilter>
in order to enumerate user group membership.
Examples: "cn" or "memberOf", etc.
Default: cn`,
},
"upndomain": {
Type: framework.TypeString,
Description: "Enables userPrincipalDomain login with [username]@UPNDomain (optional)",
},
"userattr": {
Type: framework.TypeString,
Default: "cn",
Description: "Attribute used for users (default: cn)",
},
"certificate": {
Type: framework.TypeString,
Description: "CA certificate to use when verifying LDAP server certificate, must be x509 PEM encoded (optional)",
},
"discoverdn": {
Type: framework.TypeBool,
Description: "Use anonymous bind to discover the bind DN of a user (optional)",
},
"insecure_tls": {
Type: framework.TypeBool,
Description: "Skip LDAP server SSL Certificate verification - VERY insecure (optional)",
},
"starttls": {
Type: framework.TypeBool,
Description: "Issue a StartTLS command after establishing unencrypted connection (optional)",
},
"tls_min_version": {
Type: framework.TypeString,
Default: "tls12",
Description: "Minimum TLS version to use. Accepted values are 'tls10', 'tls11' or 'tls12'. Defaults to 'tls12'",
},
"tls_max_version": {
Type: framework.TypeString,
Default: "tls12",
Description: "Maximum TLS version to use. Accepted values are 'tls10', 'tls11' or 'tls12'. Defaults to 'tls12'",
},
"deny_null_bind": {
Type: framework.TypeBool,
Default: true,
Description: "Denies an unauthenticated LDAP bind request if the user's password is empty; defaults to true",
},
"case_sensitive_names": {
Type: framework.TypeBool,
Description: "If true, case sensitivity will be used when comparing usernames and groups for matching policies.",
},
}
}
/*
* Creates and initializes a ConfigEntry object with its default values,
* as specified by the passed schema.
*/
func NewConfigEntry(d *framework.FieldData) (*ConfigEntry, error) {
cfg := new(ConfigEntry)
url := d.Get("url").(string)
if url != "" {
cfg.Url = strings.ToLower(url)
}
userattr := d.Get("userattr").(string)
if userattr != "" {
cfg.UserAttr = strings.ToLower(userattr)
}
userdn := d.Get("userdn").(string)
if userdn != "" {
cfg.UserDN = userdn
}
groupdn := d.Get("groupdn").(string)
if groupdn != "" {
cfg.GroupDN = groupdn
}
groupfilter := d.Get("groupfilter").(string)
if groupfilter != "" {
// Validate the template before proceeding
_, err := template.New("queryTemplate").Parse(groupfilter)
if err != nil {
return nil, errwrap.Wrapf("invalid groupfilter: {{err}}", err)
}
cfg.GroupFilter = groupfilter
}
groupattr := d.Get("groupattr").(string)
if groupattr != "" {
cfg.GroupAttr = groupattr
}
upndomain := d.Get("upndomain").(string)
if upndomain != "" {
cfg.UPNDomain = upndomain
}
certificate := d.Get("certificate").(string)
if certificate != "" {
block, _ := pem.Decode([]byte(certificate))
if block == nil || block.Type != "CERTIFICATE" {
return nil, fmt.Errorf("failed to decode PEM block in the certificate")
}
_, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, errwrap.Wrapf("failed to parse certificate: {{err}}", err)
}
cfg.Certificate = certificate
}
insecureTLS := d.Get("insecure_tls").(bool)
if insecureTLS {
cfg.InsecureTLS = insecureTLS
}
cfg.TLSMinVersion = d.Get("tls_min_version").(string)
if cfg.TLSMinVersion == "" {
return nil, fmt.Errorf("failed to get 'tls_min_version' value")
}
var ok bool
_, ok = tlsutil.TLSLookup[cfg.TLSMinVersion]
if !ok {
return nil, fmt.Errorf("invalid 'tls_min_version'")
}
cfg.TLSMaxVersion = d.Get("tls_max_version").(string)
if cfg.TLSMaxVersion == "" {
return nil, fmt.Errorf("failed to get 'tls_max_version' value")
}
_, ok = tlsutil.TLSLookup[cfg.TLSMaxVersion]
if !ok {
return nil, fmt.Errorf("invalid 'tls_max_version'")
}
if cfg.TLSMaxVersion < cfg.TLSMinVersion {
return nil, fmt.Errorf("'tls_max_version' must be greater than or equal to 'tls_min_version'")
}
startTLS := d.Get("starttls").(bool)
if startTLS {
cfg.StartTLS = startTLS
}
bindDN := d.Get("binddn").(string)
if bindDN != "" {
cfg.BindDN = bindDN
}
bindPass := d.Get("bindpass").(string)
if bindPass != "" {
cfg.BindPassword = bindPass
}
denyNullBind := d.Get("deny_null_bind").(bool)
if denyNullBind {
cfg.DenyNullBind = denyNullBind
}
discoverDN := d.Get("discoverdn").(bool)
if discoverDN {
cfg.DiscoverDN = discoverDN
}
caseSensitiveNames, ok := d.GetOk("case_sensitive_names")
if ok {
cfg.CaseSensitiveNames = new(bool)
*cfg.CaseSensitiveNames = caseSensitiveNames.(bool)
}
return cfg, nil
}
type ConfigEntry struct {
Url string `json:"url"`
UserDN string `json:"userdn"`
@ -34,6 +259,36 @@ type ConfigEntry struct {
CaseSensitiveNames *bool `json:"CaseSensitiveNames,omitempty"`
}
func (c *ConfigEntry) Map() map[string]interface{} {
m := c.PasswordlessMap()
m["bindpass"] = c.BindPassword
return m
}
func (c *ConfigEntry) PasswordlessMap() map[string]interface{} {
m := map[string]interface{}{
"url": c.Url,
"userdn": c.UserDN,
"groupdn": c.GroupDN,
"groupfilter": c.GroupFilter,
"groupattr": c.GroupAttr,
"upndomain": c.UPNDomain,
"userattr": c.UserAttr,
"certificate": c.Certificate,
"insecure_tls": c.InsecureTLS,
"starttls": c.StartTLS,
"binddn": c.BindDN,
"deny_null_bind": c.DenyNullBind,
"discoverdn": c.DiscoverDN,
"tls_min_version": c.TLSMinVersion,
"tls_max_version": c.TLSMaxVersion,
}
if c.CaseSensitiveNames != nil {
m["case_sensitive_names"] = *c.CaseSensitiveNames
}
return m
}
func (c *ConfigEntry) Validate() error {
if len(c.Url) == 0 {
return errors.New("at least one url must be provided")

View File

@ -138,9 +138,9 @@ func newEtcdV2Client(conf map[string]string) (client.Client, error) {
if (hasCert && hasKey) || hasCa {
var transportErr error
tls := transport.TLSInfo{
CAFile: ca,
CertFile: cert,
KeyFile: key,
TrustedCAFile: ca,
CertFile: cert,
KeyFile: key,
}
cTransport, transportErr = transport.NewTransport(tls, 30*time.Second)

View File

@ -86,9 +86,9 @@ func newEtcd3Backend(conf map[string]string, logger log.Logger) (physical.Backen
ca, hasCa := conf["tls_ca_file"]
if (hasCert && hasKey) || hasCa {
tls := transport.TLSInfo{
CAFile: ca,
CertFile: cert,
KeyFile: key,
TrustedCAFile: ca,
CertFile: cert,
KeyFile: key,
}
tlscfg, err := tls.ClientConfig()

View File

@ -32,8 +32,12 @@ govendor init
echo "Fetching deps, will take some time..."
govendor fetch +missing
# Clean up after the logrus mess
govendor remove github.com/Sirupsen/logrus
cd vendor
find -type f | grep '.go' | xargs sed -i -e 's/Sirupsen/sirupsen/'
# Need the v2 branch for Azure
govendor fetch github.com/coreos/go-oidc@v2
echo "Done; to commit run \n\ncd ${GOPATH}/src/github.com/hashicorp/${TOOL}\n"

View File

@ -16,6 +16,7 @@ export default Ember.Component.extend({
class={{buttonClasses}}
type="button"
disabled={{disabled}}
data-test-confirm-action-trigger=true
{{action 'toggleConfirm'}}
>
{{yield}}

View File

@ -0,0 +1,40 @@
import Ember from 'ember';
const { assert, inject, Component } = Ember;
export default Component.extend({
tagName: '',
flashMessages: inject.service(),
params: null,
successMessage() {
return 'Save was successful';
},
errorMessage() {
return 'There was an error saving';
},
onError(model) {
if (model && model.rollbackAttributes) {
model.rollbackAttributes();
}
},
onSuccess(){},
// override and return a promise
transaction() {
assert('override transaction call in an extension of popup-base', false);
},
actions: {
performTransaction() {
let args = [...arguments];
let messageArgs = this.messageArgs(...args);
return this.transaction(...args)
.then(() => {
this.get('onSuccess')();
this.get('flashMessages').success(this.successMessage(...messageArgs));
})
.catch(e => {
this.onError(...messageArgs);
this.get('flashMessages').success(this.errorMessage(e, ...messageArgs));
});
},
},
});

View File

@ -2,20 +2,23 @@ import Ember from 'ember';
import { task } from 'ember-concurrency';
import { humanize } from 'vault/helpers/humanize';
const { computed } = Ember;
const { computed, inject } = Ember;
export default Ember.Component.extend({
flashMessages: inject.service(),
'data-test-component': 'identity-edit-form',
model: null,
// 'create', 'edit', 'merge'
mode: 'create',
/*
* @param Function
* @public
*
* Optional param to call a function upon successfully mounting a backend
*
* Optional param to call a function upon successfully saving an entity
*/
onSave: () => {},
cancelLink: computed('mode', 'model', function() {
cancelLink: computed('mode', 'model.identityType', function() {
let { model, mode } = this.getProperties('model', 'mode');
let key = `${mode}-${model.get('identityType')}`;
let routes = {
@ -33,16 +36,17 @@ export default Ember.Component.extend({
return routes[key];
}),
getMessage(model) {
getMessage(model, isDelete = false) {
let mode = this.get('mode');
let typeDisplay = humanize([model.get('identityType')]);
let action = isDelete ? 'deleted' : 'saved';
if (mode === 'merge') {
return 'Successfully merged entities';
}
if (model.get('id')) {
return `Successfully saved ${typeDisplay} ${model.id}.`;
return `Successfully ${action} ${typeDisplay} ${model.id}.`;
}
return `Successfully saved ${typeDisplay}.`;
return `Successfully ${action} ${typeDisplay}.`;
},
save: task(function*() {
@ -56,13 +60,26 @@ export default Ember.Component.extend({
return;
}
this.get('flashMessages').success(message);
yield this.get('onSave')(model);
yield this.get('onSave')({saveType: 'save', model});
}).drop(),
willDestroy() {
let model = this.get('model');
if (!model.isDestroyed || !model.isDestroying) {
if ((model.get('isDirty') && !model.isDestroyed) || !model.isDestroying) {
model.rollbackAttributes();
}
},
actions: {
deleteItem(model) {
let message = this.getMessage(model, true);
let flash = this.get('flashMessages');
model
.destroyRecord()
.then(() => {
flash.success(message);
return this.get('onSave')({saveType: 'delete', model});
});
},
},
});

View File

@ -0,0 +1,23 @@
import Ember from 'ember';
const { inject } = Ember;
export default Ember.Component.extend({
flashMessages: inject.service(),
actions: {
enable(model) {
model.set('disabled', false);
model.save().
then(() => {
this.get('flashMessages').success(`Successfully enabled entity: ${model.id}`);
})
.catch(e => {
this.get('flashMessages').success(
`There was a problem enabling the entity: ${model.id} - ${e.error.join(' ') || e.message}`
);
});
}
}
});

View File

@ -0,0 +1,22 @@
import Base from './_popup-base';
export default Base.extend({
messageArgs(model) {
let type = model.get('identityType');
let id = model.id;
return [type, id];
},
successMessage(type, id) {
return `Successfully deleted ${type}: ${id}`;
},
errorMessage(e, type, id) {
let error = e.errors ? e.errors.join(' ') : e.message;
return `There was a problem deleting ${type}: ${id} - ${error}`;
},
transaction(model) {
return model.destroyRecord();
},
});

View File

@ -0,0 +1,34 @@
import Base from './_popup-base';
import Ember from 'ember';
const { computed } = Ember;
export default Base.extend({
model: computed.alias('params.firstObject'),
groupArray: computed('params', function() {
return this.get('params').objectAt(1);
}),
memberId: computed('params', function() {
return this.get('params').objectAt(2);
}),
messageArgs(/*model, groupArray, memberId*/) {
return [...arguments];
},
successMessage(model, groupArray, memberId) {
return `Successfully removed '${memberId}' from the group`;
},
errorMessage(e, model, groupArray, memberId) {
let error = e.errors ? e.errors.join(' ') : e.message;
return `There was a problem removing '${memberId}' from the group - ${error}`;
},
transaction(model, groupArray, memberId) {
let members = model.get(groupArray);
model.set(groupArray, members.without(memberId));
return model.save();
},
});

View File

@ -0,0 +1,29 @@
import Base from './_popup-base';
import Ember from 'ember';
const { computed } = Ember;
export default Base.extend({
model: computed.alias('params.firstObject'),
key: computed('params', function() {
return this.get('params').objectAt(1);
}),
messageArgs(model, key) {
return [model, key];
},
successMessage(model, key) {
return `Successfully removed '${key}' from metadata`;
},
errorMessage(e, model, key) {
let error = e.errors ? e.errors.join(' ') : e.message;
return `There was a problem removing '${key}' from the metadata - ${error}`;
},
transaction(model, key) {
let metadata = model.get('metadata');
delete metadata[key];
model.set('metadata', { ...metadata });
return model.save();
},
});

View File

@ -0,0 +1,29 @@
import Base from './_popup-base';
import Ember from 'ember';
const { computed } = Ember;
export default Base.extend({
model: computed.alias('params.firstObject'),
policyName: computed('params', function() {
return this.get('params').objectAt(1);
}),
messageArgs(model, policyName) {
return [model, policyName];
},
successMessage(model, policyName) {
return `Successfully removed '${policyName}' policy from ${model.id} `;
},
errorMessage(e, model, policyName) {
let error = e.errors ? e.errors.join(' ') : e.message;
return `There was a problem removing '${policyName}' policy - ${error}`;
},
transaction(model, policyName) {
let policies = model.get('policies');
model.set('policies', policies.without(policyName));
return model.save();
},
});

View File

@ -1,6 +1,7 @@
import Ember from 'ember';
export default Ember.Component.extend({
'data-test-component': 'info-table-row',
classNames: ['info-table-row'],
isVisible: Ember.computed.or('alwaysRender', 'value'),

View File

@ -6,6 +6,8 @@ const { computed } = Ember;
export default Ember.Component.extend({
type: null,
yieldWithoutColumn: false,
classNameBindings: ['containerClass'],
containerClass: computed('type', function() {

View File

@ -0,0 +1,5 @@
import Ember from 'ember';
export default Ember.Component.extend({
tagName: '',
});

View File

@ -0,0 +1,5 @@
import Ember from 'ember';
export default Ember.Component.extend({
tagName: '',
});

View File

@ -0,0 +1,5 @@
import Ember from 'ember';
export default Ember.Component.extend({
tagName: '',
});

View File

@ -0,0 +1,6 @@
import Ember from 'ember';
export default Ember.Component.extend({
tagName: '',
hasLevel: true,
});

View File

@ -0,0 +1,12 @@
import Ember from 'ember';
export default Ember.Component.extend({
tagName: '',
// api
isCertTab: false,
isConfigure: false,
baseKey: null,
backendCrumb: null,
model: null,
});

View File

@ -71,10 +71,11 @@ export default Ember.Component.extend(DEFAULTS, {
handleSuccess(resp, action) {
let props = {};
if (resp && resp.data && action === 'unwrap') {
props = Ember.assign({}, props, { unwrap_data: resp.data });
let secret = (resp && resp.data) || resp.auth;
if (secret && action === 'unwrap') {
props = Ember.assign({}, props, { unwrap_data: secret });
}
props = Ember.assign({}, props, resp.data);
props = Ember.assign({}, props, secret);
if (resp && resp.wrap_info) {
const keyName = action === 'rewrap' ? 'rewrap_token' : 'token';

View File

@ -1,4 +1,10 @@
import Ember from 'ember';
import ListController from 'vault/mixins/list-controller';
export default Ember.Controller.extend(ListController);
export default Ember.Controller.extend(ListController, {
actions: {
onDelete() {
this.send('reload');
}
}
});

View File

@ -4,7 +4,26 @@ import { task } from 'ember-concurrency';
export default Ember.Controller.extend({
showRoute: 'vault.cluster.access.identity.show',
showTab: 'details',
navToShow: task(function*(model) {
yield this.transitionToRoute(this.get('showRoute'), model.id, this.get('showTab'));
navAfterSave: task(function*({saveType, model}) {
let isDelete = saveType === 'delete';
let type = model.get('identityType');
let listRoutes= {
'entity-alias': 'vault.cluster.access.identity.aliases.index',
'group-alias': 'vault.cluster.access.identity.aliases.index',
'group': 'vault.cluster.access.identity.index',
'entity': 'vault.cluster.access.identity.index',
};
let routeName = listRoutes[type]
if (!isDelete) {
yield this.transitionToRoute(
this.get('showRoute'),
model.id,
this.get('showTab')
);
return;
}
yield this.transitionToRoute(
routeName
);
}),
});

View File

@ -1,4 +1,46 @@
import Ember from 'ember';
import ListController from 'vault/mixins/list-controller';
export default Ember.Controller.extend(ListController);
const { inject } = Ember;
export default Ember.Controller.extend(ListController, {
flashMessages: inject.service(),
actions: {
delete(model) {
let type = model.get('identityType');
let id = model.id;
return model
.destroyRecord()
.then(() => {
this.send('reload');
this.get('flashMessages').success(`Successfully deleted ${type}: ${id}`);
})
.catch(e => {
this.get('flashMessages').success(
`There was a problem deleting ${type}: ${id} - ${e.error.join(' ') || e.message}`
);
});
},
toggleDisabled(model) {
let action = model.get('disabled') ? ['enabled', 'enabling'] : ['disabled', 'disabling'];
let type = model.get('identityType');
let id = model.id;
model.toggleProperty('disabled');
model.save().
then(() => {
this.get('flashMessages').success(`Successfully ${action[0]} ${type}: ${id}`);
})
.catch(e => {
this.get('flashMessages').success(
`There was a problem ${action[1]} ${type}: ${id} - ${e.error.join(' ') || e.message}`
);
});
},
reloadRecord(model) {
model.reload();
},
},
});

View File

@ -1,9 +1,11 @@
import Ember from 'ember';
import utils from 'vault/lib/key-utils';
export default Ember.Controller.extend({
flashMessages: Ember.inject.service(),
clusterController: Ember.inject.controller('vault.cluster'),
const { inject, computed, Controller } = Ember;
export default Controller.extend({
flashMessages: inject.service(),
store: inject.service(),
clusterController: inject.controller('vault.cluster'),
queryParams: {
page: 'page',
pageFilter: 'pageFilter',
@ -13,7 +15,7 @@ export default Ember.Controller.extend({
pageFilter: null,
filter: null,
backendCrumb: Ember.computed(function() {
backendCrumb: computed(function() {
return {
label: 'leases',
text: 'leases',
@ -24,13 +26,13 @@ export default Ember.Controller.extend({
isLoading: false,
filterMatchesKey: Ember.computed('filter', 'model', 'model.[]', function() {
filterMatchesKey: computed('filter', 'model', 'model.[]', function() {
var filter = this.get('filter');
var content = this.get('model');
return !!(content.length && content.findBy('id', filter));
}),
firstPartialMatch: Ember.computed('filter', 'model', 'model.[]', 'filterMatchesKey', function() {
firstPartialMatch: computed('filter', 'model', 'model.[]', 'filterMatchesKey', function() {
var filter = this.get('filter');
var content = this.get('model');
var filterMatchesKey = this.get('filterMatchesKey');
@ -42,7 +44,7 @@ export default Ember.Controller.extend({
});
}),
filterIsFolder: Ember.computed('filter', function() {
filterIsFolder: computed('filter', function() {
return !!utils.keyIsFolder(this.get('filter'));
}),
@ -56,7 +58,7 @@ export default Ember.Controller.extend({
},
revokePrefix(prefix, isForce) {
const adapter = this.model.store.adapterFor('lease');
const adapter = this.get('store').adapterFor('lease');
const method = isForce ? 'forceRevokePrefix' : 'revokePrefix';
const fn = adapter[method];
fn

View File

@ -66,6 +66,7 @@ export default Ember.Controller.extend(BackendCrumbMixin, {
delete(item) {
const name = item.id;
item.destroyRecord().then(() => {
this.send('reload');
this.get('flashMessages').success(`${name} was successfully deleted.`);
});
},

View File

@ -30,7 +30,6 @@ export default Ember.Controller.extend({
description: null,
default_lease_ttl: null,
max_lease_ttl: null,
force_no_cache: null,
showConfig: false,
local: false,
sealWrap: false,
@ -50,7 +49,6 @@ export default Ember.Controller.extend({
description: null,
default_lease_ttl: null,
max_lease_ttl: null,
force_no_cache: null,
local: false,
showConfig: false,
sealWrap: false,
@ -82,7 +80,6 @@ export default Ember.Controller.extend({
selectedType: type,
description,
default_lease_ttl,
force_no_cache,
local,
max_lease_ttl,
sealWrap,
@ -92,7 +89,6 @@ export default Ember.Controller.extend({
'selectedType',
'description',
'default_lease_ttl',
'force_no_cache',
'local',
'max_lease_ttl',
'sealWrap',
@ -112,9 +108,8 @@ export default Ember.Controller.extend({
if (this.get('showConfig')) {
attrs.config = {
default_lease_ttl,
max_lease_ttl,
force_no_cache,
defaultLeaseTtl: default_lease_ttl,
maxLeaseTtl: max_lease_ttl,
};
}

View File

@ -0,0 +1,5 @@
import lazyCapabilities, { apiPath } from 'vault/macros/lazy-capabilities';
export default function() {
return lazyCapabilities(apiPath`identity/${'identityType'}/id/${'id'}`, 'id', 'identityType');
}

View File

@ -0,0 +1,25 @@
import { queryRecord } from 'ember-computed-query';
export function apiPath(strings, ...keys) {
return function(data) {
let dict = data || {};
let result = [strings[0]];
keys.forEach((key, i) => {
result.push(dict[key], strings[i + 1]);
});
return result.join('');
};
}
export default function() {
let [templateFn, ...keys] = arguments;
return queryRecord(
'capabilities',
context => {
return {
id: templateFn(context.getProperties(...keys)),
};
},
...keys
);
}

View File

@ -1,8 +1,12 @@
import IdentityModel from './_base';
import DS from 'ember-data';
import Ember from 'ember';
import identityCapabilities from 'vault/macros/identity-capabilities';
const { attr, belongsTo } = DS;
const { computed } = Ember;
export default IdentityModel.extend({
parentType: 'entity',
formFields: ['name', 'mountAccessor', 'metadata'],
entity: belongsTo('identity/entity', { readOnly: true, async: false }),
@ -12,7 +16,7 @@ export default IdentityModel.extend({
label: 'Auth Backend',
editType: 'mountAccessor',
}),
metadata: attr('object', {
metadata: attr({
editType: 'kv',
}),
mountPath: attr('string', {
@ -28,4 +32,8 @@ export default IdentityModel.extend({
readOnly: true,
}),
mergedFromCanonicalIds: attr(),
updatePath: identityCapabilities(),
canDelete: computed.alias('updatePath.canDelete'),
canEdit: computed.alias('updatePath.canUpdate'),
});

View File

@ -1,12 +1,23 @@
import Ember from 'ember';
import IdentityModel from './_base';
import DS from 'ember-data';
import lazyCapabilities, { apiPath } from 'vault/macros/lazy-capabilities';
import identityCapabilities from 'vault/macros/identity-capabilities';
const { computed } = Ember;
const { attr, hasMany } = DS;
export default IdentityModel.extend({
formFields: ['name', 'policies', 'metadata'],
formFields: ['name', 'disabled', 'policies', 'metadata'],
name: attr('string'),
disabled: attr('boolean', {
defaultValue: false,
label: 'Disable entity',
helpText: 'All associated tokens cannot be used, but are not revoked.',
}),
mergedEntityIds: attr(),
metadata: attr('object', {
metadata: attr({
editType: 'kv',
}),
policies: attr({
@ -28,4 +39,11 @@ export default IdentityModel.extend({
inheritedGroupIds: attr({
readOnly: true,
}),
updatePath: identityCapabilities(),
canDelete: computed.alias('updatePath.canDelete'),
canEdit: computed.alias('updatePath.canUpdate'),
aliasPath: lazyCapabilities(apiPath`identity/entity-alias`),
canAddAlias: computed.alias('aliasPath.canCreate'),
});

View File

@ -1,8 +1,13 @@
import IdentityModel from './_base';
import DS from 'ember-data';
import Ember from 'ember';
import identityCapabilities from 'vault/macros/identity-capabilities';
const { attr, belongsTo } = DS;
const { computed } = Ember;
export default IdentityModel.extend({
parentType: 'group',
formFields: ['name', 'mountAccessor'],
group: belongsTo('identity/group', { readOnly: true, async: false }),
@ -26,4 +31,9 @@ export default IdentityModel.extend({
lastUpdateTime: attr('string', {
readOnly: true,
}),
updatePath: identityCapabilities(),
canDelete: computed.alias('updatePath.canDelete'),
canEdit: computed.alias('updatePath.canUpdate'),
});

View File

@ -1,6 +1,8 @@
import Ember from 'ember';
import IdentityModel from './_base';
import DS from 'ember-data';
import lazyCapabilities, { apiPath } from 'vault/macros/lazy-capabilities';
import identityCapabilities from 'vault/macros/identity-capabilities';
const { computed } = Ember;
const { attr, belongsTo } = DS;
@ -52,4 +54,18 @@ export default IdentityModel.extend({
),
alias: belongsTo('identity/group-alias', { async: false, readOnly: true }),
updatePath: identityCapabilities(),
canDelete: computed.alias('updatePath.canDelete'),
canEdit: computed.alias('updatePath.canUpdate'),
aliasPath: lazyCapabilities(apiPath`identity/group-alias`),
canAddAlias: computed('aliasPath.canCreate', 'type', 'alias', function() {
let type = this.get('type');
let alias = this.get('alias');
// internal groups can't have aliases, and external groups can only have one
if (type === 'internal' || alias) {
return false;
}
return this.get('aliasPath.canCreate');
}),
});

View File

@ -2,5 +2,7 @@ import attr from 'ember-data/attr';
import Fragment from 'ember-data-model-fragments/fragment';
export default Fragment.extend({
version: attr('number'),
version: attr('number', {
label: 'Version',
}),
});

View File

@ -3,6 +3,8 @@ import DS from 'ember-data';
import { queryRecord } from 'ember-computed-query';
import { fragment } from 'ember-data-model-fragments/attributes';
import { expandAttributeMeta } from 'vault/utils/field-to-attrs';
const { attr } = DS;
const { computed } = Ember;
@ -16,11 +18,26 @@ export default DS.Model.extend({
name: attr('string'),
type: attr('string'),
description: attr('string'),
config: attr('object'),
options: fragment('mount-options'),
config: fragment('mount-config', { defaultValue: {} }),
options: fragment('mount-options', { defaultValue: {} }),
local: attr('boolean'),
sealWrap: attr('boolean'),
formFields: [
'type',
'path',
'description',
'accessor',
'local',
'sealWrap',
'config.{defaultLeaseTtl,maxLeaseTtl}',
'options.{version}',
],
attrs: computed('formFields', function() {
return expandAttributeMeta(this, this.get('formFields'));
}),
shouldIncludeInList: computed('type', function() {
return !LIST_EXCLUDED_BACKENDS.includes(this.get('type'));
}),

View File

@ -68,6 +68,7 @@ Router.map(function() {
this.route('backends', { path: '/' });
this.route('backend', { path: '/:backend' }, function() {
this.route('index', { path: '/' });
this.route('configuration');
// because globs / params can't be empty,
// we have to special-case ids of '' with thier own routes
this.route('list-root', { path: '/list/' });

View File

@ -1,6 +1,8 @@
import Ember from 'ember';
import UnloadModelRoute from 'vault/mixins/unload-model-route';
import UnsavedModelRoute from 'vault/mixins/unsaved-model-route';
export default Ember.Route.extend({
export default Ember.Route.extend(UnloadModelRoute, UnsavedModelRoute, {
model(params) {
let itemType = this.modelFor('vault.cluster.access.identity');
let modelType = `identity/${itemType}-alias`;

View File

@ -1,6 +1,8 @@
import Ember from 'ember';
import UnloadModelRoute from 'vault/mixins/unload-model-route';
import UnsavedModelRoute from 'vault/mixins/unsaved-model-route';
export default Ember.Route.extend({
export default Ember.Route.extend(UnloadModelRoute, UnsavedModelRoute, {
model(params) {
let itemType = this.modelFor('vault.cluster.access.identity');
let modelType = `identity/${itemType}-alias`;

View File

@ -27,10 +27,14 @@ export default Ember.Route.extend(ListRoute, {
actions: {
willTransition(transition) {
window.scrollTo(0, 0);
if (transition.targetName !== this.routeName) {
if (!transition || transition.targetName !== this.routeName) {
this.store.clearAllDatasets();
}
return true;
},
reload() {
this.store.clearAllDatasets();
this.refresh();
}
},
});

View File

@ -1,6 +1,8 @@
import Ember from 'ember';
import UnloadModelRoute from 'vault/mixins/unload-model-route';
import UnsavedModelRoute from 'vault/mixins/unsaved-model-route';
export default Ember.Route.extend({
export default Ember.Route.extend(UnloadModelRoute, UnsavedModelRoute, {
model() {
let itemType = this.modelFor('vault.cluster.access.identity');
let modelType = `identity/${itemType}`;

View File

@ -1,6 +1,8 @@
import Ember from 'ember';
import UnloadModelRoute from 'vault/mixins/unload-model-route';
import UnsavedModelRoute from 'vault/mixins/unsaved-model-route';
export default Ember.Route.extend({
export default Ember.Route.extend(UnloadModelRoute, UnsavedModelRoute, {
model(params) {
let itemType = this.modelFor('vault.cluster.access.identity');
let modelType = `identity/${itemType}`;

View File

@ -34,5 +34,9 @@ export default Ember.Route.extend(ListRoute, {
}
return true;
},
reload() {
this.store.clearAllDatasets();
this.refresh();
}
},
});

View File

@ -13,13 +13,36 @@ export default Ember.Route.extend({
Ember.set(error, 'httpStatus', 404);
throw error;
}
// TODO peekRecord here to see if we have the record already
// if the record is in the store use that
let model = this.store.peekRecord(modelType, params.item_id);
// if we don't have creationTime, we only have a partial model so reload
if (model && !model.get('creationTime')) {
model = model.reload();
}
// if there's no model, we need to fetch it
if (!model) {
model = this.store.findRecord(modelType, params.item_id);
}
return Ember.RSVP.hash({
model: this.store.findRecord(modelType, params.item_id),
model,
section,
});
},
activate() {
// if we're just entering the route, and it's not a hard reload
// reload to make sure we have the newest info
if (this.currentModel) {
Ember.run.next(() => {
this.controller.get('model').reload();
});
}
},
afterModel(resolvedModel) {
let { section, model } = resolvedModel;
if (model.get('identityType') === 'group' && model.get('type') === 'internal' && section === 'aliases') {

View File

@ -0,0 +1,7 @@
import Ember from 'ember';
export default Ember.Route.extend({
model() {
return this.modelFor('vault.cluster.secrets.backend');
},
});

View File

@ -159,5 +159,9 @@ export default Ember.Route.extend({
}
return true;
},
reload() {
this.refresh();
this.store.clearAllDatasets();
}
},
});

View File

@ -131,13 +131,25 @@ export default DS.Store.extend({
// pushes records into the store and returns the result
fetchPage(modelName, query) {
const response = this.constructResponse(modelName, query);
this.unloadAll(modelName);
this.push(
this.serializerFor(modelName).normalizeResponse(this, this.modelFor(modelName), response, null, 'query')
);
const model = this.peekAll(modelName);
model.set('meta', response.meta);
return model;
this.peekAll(modelName).forEach(record => {
record.unloadRecord();
});
return new Ember.RSVP.Promise(resolve => {
Ember.run.schedule('destroy', () => {
this.push(
this.serializerFor(modelName).normalizeResponse(
this,
this.modelFor(modelName),
response,
null,
'query'
)
);
let model = this.peekAll(modelName).toArray();
model.set('meta', response.meta);
resolve(model);
});
});
},
// get cached data

View File

@ -20,8 +20,7 @@
}
}
.popup-menu-trigger {
width: 3rem;
height: 2rem;
min-width: auto;
}
.popup-menu-trigger.is-active {
&,
@ -50,6 +49,7 @@
height: auto;
width: 100%;
text-align: left;
text-decoration: none;
&:hover {
background-color: $menu-item-hover-background-color;

View File

@ -133,6 +133,17 @@ $button-box-shadow-standard: 0 3px 1px 0 rgba($black, 0.12);
}
}
&.is-orange {
background-color: $orange;
border-color: $orange;
color: $white;
&:hover,
&.is-hovered {
background-color: darken($orange, 5%);
border-color: darken($orange, 5%);
}
}
&.is-compact {
height: 2rem;
padding: $size-11 $size-8;
@ -146,7 +157,6 @@ $button-box-shadow-standard: 0 3px 1px 0 rgba($black, 0.12);
}
}
&.is-more-icon,
&.tool-tip-trigger {
color: $black;
min-width: auto;

View File

@ -40,7 +40,7 @@
{{/if}}
</li>
<li class="{{if (is-active-route (array 'vault.cluster.policies' 'vault.cluster.policy')) 'is-active'}}">
<a href="{{href-to "vault.cluster.policies" activeClusterName current-when='vault.cluster.policies vault.cluster.policy'}}">
<a href="{{href-to "vault.cluster.policies" "acl" current-when='vault.cluster.policies vault.cluster.policy'}}">
Policies
</a>
</li>

View File

@ -10,24 +10,39 @@
{{form-field data-test-field attr=attr model=model}}
{{/each}}
</div>
<div class="field is-grouped box is-fullwidth is-bottomless">
<div class="control">
<button type="submit" data-test-identity-submit=true class="button is-primary {{if save.isRunning 'loading'}}" disabled={{save.isRunning}}>
{{#if (eq mode "create")}}
Create
<div class="field is-grouped is-grouped-split is-fullwidth box is-bottomless">
<div class="field is-grouped">
<div class="control">
<button type="submit" data-test-identity-submit=true class="button is-primary {{if save.isRunning 'loading'}}" disabled={{save.isRunning}}>
{{#if (eq mode "create")}}
Create
{{else}}
Save
{{/if}}
</button>
{{#if (or (eq mode "merge") (eq mode "create" ))}}
<a href={{href-to cancelLink}} class="button" data-test-cancel-link>
Cancel
</a>
{{else}}
Save
<a href={{href-to cancelLink model.id "details"}} class="button" data-test-cancel-link>
Cancel
</a>
{{/if}}
</button>
{{#if (or (eq mode "merge") (eq mode "create" ))}}
<a href={{href-to cancelLink}} class="button">
Cancel
</a>
{{else}}
<a href={{href-to cancelLink model.id "details"}} class="button">
Cancel
</a>
{{/if}}
</div>
</div>
{{#if (and (eq mode "edit") model.canDelete)}}
{{#confirm-action
buttonClasses="button is-ghost"
onConfirmAction=(action "deleteItem" model)
confirmMessage=(concat "Are you sure you want to delete " model.id "?")
data-test-entity-item-delete=true
}}
Delete
{{/confirm-action}}
{{/if}}
</div>
</form>

View File

@ -7,12 +7,12 @@
</div>
<div class="level-right">
{{#if (eq identityType "entity")}}
<a href="{{href-to 'vault.cluster.access.identity.merge'}}" class="button has-icon-right is-ghost is-compact" data-test-entity-merge-link=true>
<a href="{{href-to 'vault.cluster.access.identity.merge' (pluralize identityType)}}" class="button has-icon-right is-ghost is-compact" data-test-entity-merge-link=true>
Merge {{pluralize identityType}}
{{i-con glyph="chevron-right" size=11}}
</a>
{{/if}}
<a href="{{href-to 'vault.cluster.access.identity.create'}}" class="button has-icon-right is-ghost is-compact" data-test-entity-create-link=true>
<a href="{{href-to 'vault.cluster.access.identity.create' (pluralize identityType)}}" class="button has-icon-right is-ghost is-compact" data-test-entity-create-link=true>
Create {{identityType}}
{{i-con glyph="chevron-right" size=11}}
</a>

View File

@ -1,4 +1,4 @@
{{info-table-row label="Name" value=model.name }}
{{info-table-row label="Name" value=model.name data-test-alias-name=true}}
{{info-table-row label="ID" value=model.id }}
{{#info-table-row label=(if (eq model.identityType "entity-alias") "Entity ID" "Group ID") value=model.canonicalId}}
<a href={{href-to 'vault.cluster.access.identity.show' (if (eq model.identityType "entity-alias") "entities" "groups") model.canonicalId "details"}}

View File

@ -8,6 +8,9 @@
{{value}}
</div>
<div class="column has-text-right">
{{#if model.canEdit}}
{{identity/popup-metadata params=(array model key)}}
{{/if}}
</div>
</div>
</div>

View File

@ -18,6 +18,7 @@
<code class="has-text-grey is-size-8">{{item.mountAccessor}}</code>
</div>
<div class="column has-text-right">
{{identity/popup-alias params=(array item)}}
</div>
</div>
{{/linked-block}}

View File

@ -1,4 +1,20 @@
{{info-table-row label="Name" value=model.name }}
{{#if model.disabled}}
<div class="box is-shadowless is-marginless">
{{#message-in-page type="warning" yieldWithoutColumn=true messageClass="message-body is-marginless" data-test-disabled-warning=true}}
<div class="column">
<strong>Attention</strong> This {{model.identityType}} is disabled. All associated tokens cannot be used, but are not revoked.
</div>
{{#if model.canEdit}}
<div class="column is-flex-v-centered is-narrow">
<button type="button" class="button is-orange box" {{action "enable" model}} data-test-enable=true>
Enable
</button>
</div>
{{/if}}
{{/message-in-page}}
</div>
{{/if}}
{{info-table-row label="Name" value=model.name data-test-identity-item-name=true}}
{{info-table-row label="Type" value=model.type }}
{{info-table-row label="ID" value=model.id }}
{{#info-table-row label="Merged Ids" value=model.mergedEntityIds }}

View File

@ -1,21 +1,56 @@
{{#if model.hasMembers}}
{{#each model.memberGroupIds as |gid|}}
<a href={{href-to "vault.cluster.access.identity.show" "groups" gid "details" }}
class="box is-sideless is-marginless"
>{{i-con
glyph='folder'
size=14
class="has-text-grey-light"
}}{{gid}}</a>
{{#linked-block
"vault.cluster.access.identity.show"
"groups"
gid
details
class="box is-sideless is-marginless"
}}
<div class="columns is-mobile">
<div class="column is-10">
<a href={{href-to "vault.cluster.access.identity.show" "groups" gid "details" }}
class="is-block has-text-black has-text-weight-semibold"
>{{i-con
glyph='folder'
size=14
class="has-text-grey-light"
}}{{gid}}</a>
</div>
<div class="column has-text-right">
{{#if model.canEdit}}
{{identity/popup-members params=(array model "memberGroupIds" gid)}}
{{/if}}
</div>
</div>
{{/linked-block}}
{{/each}}
{{#each model.memberEntityIds as |gid|}}
<a href={{href-to "vault.cluster.access.identity.show" "entities" gid "details" }}
{{#linked-block
"vault.cluster.access.identity.show"
"groups"
gid
details
class="box is-sideless is-marginless"
>{{i-con
glyph='role'
size=14
class="has-text-grey-light"
}}{{gid}}</a>
}}
<div class="columns">
<div class="column is-10">
<a href={{href-to "vault.cluster.access.identity.show" "entities" gid "details" }}
class="is-block has-text-black has-text-weight-semibold"
>{{i-con
glyph='role'
size=14
class="has-text-grey-light"
}}{{gid}}</a>
</div>
<div class="column has-text-right">
{{#if model.canEdit}}
{{identity/popup-members params=(array model "memberEntityIds" gid)}}
{{/if}}
</div>
</div>
{{/linked-block}}
{{/each}}
{{else}}
<div class="box is-bottomless has-background-white-bis">

View File

@ -8,6 +8,9 @@
{{value}}
</div>
<div class="column has-text-right">
{{#if model.canEdit}}
{{identity/popup-metadata params=(array model key)}}
{{/if}}
</div>
</div>
</div>

View File

@ -1,4 +1,4 @@
{{#each model.policies as |item|}}
{{#each model.policies as |policyName|}}
{{#linked-block
"vault.cluster.policy.show"
"acl"
@ -7,12 +7,15 @@
}}
<div class="columns is-mobile">
<div class="column is-10">
<a href={{href-to "vault.cluster.policy.show" "acl" item}}
class="has-text-black has-text-weight-semibold"
><span class="is-underline">{{item}}</span>
<a href={{href-to "vault.cluster.policy.show" "acl" policyName}}
class="is-block has-text-black has-text-weight-semibold"
><span class="is-underline">{{policyName}}</span>
</a>
</div>
<div class="column has-text-right">
{{#if model.canEdit}}
{{identity/popup-policy params=(array model policyName)}}
{{/if}}
</div>
</div>
{{/linked-block}}

View File

@ -0,0 +1,45 @@
{{#popup-menu name="alias-menu"}}
{{#with params.firstObject as |item|}}
<nav class="menu">
<ul class="menu-list">
<li class="action">
<a href={{href-to "vault.cluster.access.identity.aliases.show" (pluralize item.parentType) item.id "details" }}>
Details
</a>
</li>
{{#if item.updatePath.isPending}}
<li class="action">
<button disabled=true type="button" class="link button is-loading is-transparent">
loading
</button>
</li>
{{else}}
{{#if item.canEdit}}
<li class="action">
<a href={{href-to "vault.cluster.access.identity.aliases.edit" (pluralize item.parentType) item.id}}>
Edit
</a>
</li>
{{/if}}
{{#if item.canDelete}}
<li class="action">
{{#confirm-action
data-test-item-delete=true
confirmButtonClasses="button is-primary"
buttonClasses="link"
onConfirmAction=(action "performTransaction" item)
confirmMessage=(concat "Are you sure you want to delete " item.id "?")
showConfirm=(get this (concat "shouldDelete-" item.id))
class=(if (get this (concat "shouldDelete-" item.id)) "message is-block is-warning is-outline")
containerClasses="message-body is-block"
messageClasses="is-block"
}}
Delete
{{/confirm-action}}
</li>
{{/if}}
{{/if}}
</ul>
</nav>
{{/with}}
{{/popup-menu}}

View File

@ -0,0 +1,21 @@
{{#popup-menu name="member-edit-menu"}}
<nav class="menu">
<ul class="menu-list">
<li class="action">
{{#confirm-action
confirmButtonClasses="button is-primary"
confirmButtonText="Remove"
buttonClasses="link"
onConfirmAction=(action "performTransaction" model groupArray memberId)
confirmMessage=(concat "Are you sure you want to remove " memberId "?")
showConfirm=(get this (concat "shouldDelete-" memberId))
class=(if (get this (concat "shouldDelete-" memberId)) "message is-block is-warning is-outline")
containerClasses="message-body is-block"
messageClasses="is-block"
}}
Remove
{{/confirm-action}}
</li>
</ul>
</nav>
{{/popup-menu}}

View File

@ -0,0 +1,21 @@
{{#popup-menu name="metadata-edit-menu"}}
<nav class="menu">
<ul class="menu-list">
<li class="action">
{{#confirm-action
confirmButtonClasses="button is-primary"
confirmButtonText="Remove"
buttonClasses="link"
onConfirmAction=(action "performTransaction" model key)
confirmMessage=(concat "Are you sure you want to remove " key "?")
showConfirm=(get this (concat "shouldDelete-" key))
class=(if (get this (concat "shouldDelete-" key)) "message is-block is-warning is-outline")
containerClasses="message-body is-block"
messageClasses="is-block"
}}
Remove
{{/confirm-action}}
</li>
</ul>
</nav>
{{/popup-menu}}

View File

@ -0,0 +1,31 @@
{{#popup-menu name="policy-menu"}}
<nav class="menu">
<ul class="menu-list">
<li class="action">
<a href={{href-to "vault.cluster.policy.show" "acl" policyName }}>
View Policy
</a>
</li>
<li class="action">
<a href={{href-to "vault.cluster.policy.edit" "acl" policyName }}>
Edit Policy
</a>
</li>
<li class="action">
{{#confirm-action
confirmButtonClasses="button is-primary"
confirmButtonText="Remove"
buttonClasses="link"
onConfirmAction=(action "performTransaction" model policyName)
confirmMessage=(concat "Are you sure you want to remove " policyName "?")
showConfirm=(get this (concat "shouldDelete-" policyName))
class=(if (get this (concat "shouldDelete-" policyName)) "message is-block is-warning is-outline")
containerClasses="message-body is-block"
messageClasses="is-block"
}}
Remove from {{model.identityType}}
{{/confirm-action}}
</li>
</ul>
</nav>
{{/popup-menu}}

View File

@ -1,4 +1,5 @@
<ul>
{{yield}}
{{#each secretPath as |path index|}}
<li class="{{if (is-active-route path.path path.model isExact=true) 'is-active'}}">
<span class="sep">&#x0002f;</span>

View File

@ -8,11 +8,15 @@
excludeIconClass=true
}}
</div>
<div class="column">
<p>
<strong>{{alertType.text}}</strong>
{{#if yieldWithoutColumn}}
{{yield}}
</p>
</div>
{{else}}
<div class="column">
<p>
<strong>{{alertType.text}}</strong>
{{yield}}
</p>
</div>
{{/if}}
</div>
</div>

View File

@ -0,0 +1 @@
{{yield}}

View File

@ -0,0 +1 @@
{{yield}}

Some files were not shown because too many files have changed in this diff Show More