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: 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 * Cert auth CIDR restrictions: When using the `cert` auth method you can now
limit authentication to specific CIDRs; these will also be encoded in limit authentication to specific CIDRs; these will also be encoded in
resultant tokens to limit their use. 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: IMPROVEMENTS:
* api: Close renewer's doneCh when the renewer is stopped, so that programs * api: Close renewer's doneCh when the renewer is stopped, so that programs
expecting a final value through doneCh behave correctly [GH-4472] 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 * cli: `vault login` now supports a `-no-print` flag to suppress printing
token information but still allow storing into the token helper [GH-4454] 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 CKM_RSA_PKCS_OAEP mechanisms
* core/pkcs11 (enterprise): HSM slots can now be selected by token label instead * core/pkcs11 (enterprise): HSM slots can now be selected by token label instead
of just slot number 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 * expiration: Allow revoke-prefix and revoke-force to work on single leases as
well as prefixes [GH-4450] well as prefixes [GH-4450]
@ -39,12 +45,19 @@ BUG FIXES:
parameters were used [GH-4582] parameters were used [GH-4582]
* secret/gcp: Make `bound_region` able to use short names * secret/gcp: Make `bound_region` able to use short names
* secret/kv: Fix response wrapping for KV v2 [GH-4511] * 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 `key_type` not being allowed to be set to `any` [GH-4595]
* secret/pki: Fix path length parameter being ignored when using * secret/pki: Fix path length parameter being ignored when using
`use_csr_values` and signing an intermediate CA cert [GH-4459] `use_csr_values` and signing an intermediate CA cert [GH-4459]
* storage/dynamodb: Fix listing when one child is left within a nested path * storage/dynamodb: Fix listing when one child is left within a nested path
[GH-4570] [GH-4570]
* ui: Fix HMAC algorithm in transit [GH-4604] * 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) ## 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() c.modifyLock.Lock()
defer c.modifyLock.Unlock() defer c.modifyLock.Unlock()
var err error parsedAddr, err := url.Parse(addr)
if c.addr, err = url.Parse(addr); err != nil { if err != nil {
return errwrap.Wrapf("failed to set address: {{err}}", err) return errwrap.Wrapf("failed to set address: {{err}}", err)
} }
c.addr = parsedAddr
return nil return nil
} }
@ -411,7 +412,8 @@ func (c *Client) SetLimiter(rateLimit float64, burst int) {
c.modifyLock.RLock() c.modifyLock.RLock()
c.config.modifyLock.Lock() c.config.modifyLock.Lock()
defer c.config.modifyLock.Unlock() defer c.config.modifyLock.Unlock()
defer c.modifyLock.RUnlock() c.modifyLock.RUnlock()
c.config.Limiter = rate.NewLimiter(rate.Limit(rateLimit), burst) 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. // doesn't need to be called externally.
func (c *Client) NewRequest(method, requestPath string) *Request { func (c *Client) NewRequest(method, requestPath string) *Request {
c.modifyLock.RLock() 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 // 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 // record and take the highest match; this is not designed for high-availability, just discovery
var host string = c.addr.Host var host string = addr.Host
if c.addr.Port() == "" { if addr.Port() == "" {
// Internet Draft specifies that the SRV record is ignored if a port is given // 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 { if err == nil && len(addrs) > 0 {
host = fmt.Sprintf("%s:%d", addrs[0].Target, addrs[0].Port) 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{ req := &Request{
Method: method, Method: method,
URL: &url.URL{ URL: &url.URL{
User: c.addr.User, User: addr.User,
Scheme: c.addr.Scheme, Scheme: addr.Scheme,
Host: host, 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), Params: make(map[string][]string),
} }
@ -579,21 +587,19 @@ func (c *Client) NewRequest(method, requestPath string) *Request {
lookupPath = requestPath lookupPath = requestPath
} }
req.MFAHeaderVals = c.mfaCreds req.MFAHeaderVals = mfaCreds
if c.wrappingLookupFunc != nil { if wrappingLookupFunc != nil {
req.WrapTTL = c.wrappingLookupFunc(method, lookupPath) req.WrapTTL = wrappingLookupFunc(method, lookupPath)
} else { } else {
req.WrapTTL = DefaultWrappingLookupFunc(method, lookupPath) req.WrapTTL = DefaultWrappingLookupFunc(method, lookupPath)
} }
if c.config.Timeout != 0 {
c.config.HttpClient.Timeout = c.config.Timeout if headers != nil {
} req.Headers = headers
if c.headers != nil {
req.Headers = c.headers
} }
req.PolicyOverride = c.policyOverride req.PolicyOverride = policyOverride
return req 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 // a Vault server not configured with this client. This is an advanced operation
// that generally won't need to be called externally. // that generally won't need to be called externally.
func (c *Client) RawRequest(r *Request) (*Response, error) { func (c *Client) RawRequest(r *Request) (*Response, error) {
c.modifyLock.RLock() 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 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() c.modifyLock.RUnlock()
if limiter != nil {
limiter.Wait(context.Background())
}
// Sanity check the token before potentially erroring from the API // Sanity check the token before potentially erroring from the API
idx := strings.IndexFunc(token, func(c rune) bool { idx := strings.IndexFunc(token, func(c rune) bool {
return !unicode.IsPrint(c) return !unicode.IsPrint(c)
@ -632,16 +643,23 @@ START:
return nil, fmt.Errorf("nil request created") 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 { if backoff == nil {
backoff = retryablehttp.LinearJitterBackoff backoff = retryablehttp.LinearJitterBackoff
} }
client := &retryablehttp.Client{ client := &retryablehttp.Client{
HTTPClient: c.config.HttpClient, HTTPClient: httpClient,
RetryWaitMin: 1000 * time.Millisecond, RetryWaitMin: 1000 * time.Millisecond,
RetryWaitMax: 1500 * time.Millisecond, RetryWaitMax: 1500 * time.Millisecond,
RetryMax: c.config.MaxRetries, RetryMax: maxRetries,
CheckRetry: retryablehttp.DefaultRetryPolicy, CheckRetry: retryablehttp.DefaultRetryPolicy,
Backoff: backoff, Backoff: backoff,
ErrorHandler: retryablehttp.PassthroughErrorHandler, ErrorHandler: retryablehttp.PassthroughErrorHandler,
@ -649,6 +667,9 @@ START:
var result *Response var result *Response
resp, err := client.Do(req) resp, err := client.Do(req)
if cancelFunc != nil {
cancelFunc()
}
if resp != nil { if resp != nil {
result = &Response{Response: resp} result = &Response{Response: resp}
} }

View File

@ -7,7 +7,6 @@ import (
"os" "os"
"strings" "strings"
"testing" "testing"
"time"
) )
func init() { func init() {
@ -244,22 +243,10 @@ func TestClientTimeoutSetting(t *testing.T) {
defer os.Setenv(EnvVaultClientTimeout, oldClientTimeout) defer os.Setenv(EnvVaultClientTimeout, oldClientTimeout)
config := DefaultConfig() config := DefaultConfig()
config.ReadEnvironment() config.ReadEnvironment()
client, err := NewClient(config) _, err := NewClient(config)
if err != nil { if err != nil {
t.Fatal(err) 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) type roundTripperFunc func(*http.Request) (*http.Response, error)

View File

@ -843,9 +843,9 @@ func TestBackend_CertWrites(t *testing.T) {
tc := logicaltest.TestCase{ tc := logicaltest.TestCase{
Backend: testFactory(t), Backend: testFactory(t),
Steps: []logicaltest.TestStep{ Steps: []logicaltest.TestStep{
testAccStepCert(t, "aaa", ca1, "foo", "", "", false), testAccStepCert(t, "aaa", ca1, "foo", allowed{}, false),
testAccStepCert(t, "bbb", ca2, "foo", "", "", false), testAccStepCert(t, "bbb", ca2, "foo", allowed{}, false),
testAccStepCert(t, "ccc", ca3, "foo", "", "", true), testAccStepCert(t, "ccc", ca3, "foo", allowed{}, true),
}, },
} }
tc.Steps = append(tc.Steps, testAccStepListCerts(t, []string{"aaa", "bbb"})...) 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{ logicaltest.Test(t, logicaltest.TestCase{
Backend: testFactory(t), Backend: testFactory(t),
Steps: []logicaltest.TestStep{ Steps: []logicaltest.TestStep{
testAccStepCert(t, "web", ca, "foo", "", "", false), testAccStepCert(t, "web", ca, "foo", allowed{}, false),
testAccStepLogin(t, connState), testAccStepLogin(t, connState),
testAccStepCertLease(t, "web", ca, "foo"), testAccStepCertLease(t, "web", ca, "foo"),
testAccStepCertTTL(t, "web", ca, "foo"), testAccStepCertTTL(t, "web", ca, "foo"),
@ -875,9 +875,9 @@ func TestBackend_basic_CA(t *testing.T) {
testAccStepLogin(t, connState), testAccStepLogin(t, connState),
testAccStepCertNoLease(t, "web", ca, "foo"), testAccStepCertNoLease(t, "web", ca, "foo"),
testAccStepLoginDefaultLease(t, connState), testAccStepLoginDefaultLease(t, connState),
testAccStepCert(t, "web", ca, "foo", "*.example.com", "", false), testAccStepCert(t, "web", ca, "foo", allowed{names: "*.example.com"}, false),
testAccStepLogin(t, connState), testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", "*.invalid.com", "", false), testAccStepCert(t, "web", ca, "foo", allowed{names: "*.invalid.com"}, false),
testAccStepLoginInvalid(t, connState), testAccStepLoginInvalid(t, connState),
}, },
}) })
@ -926,20 +926,45 @@ func TestBackend_basic_singleCert(t *testing.T) {
logicaltest.Test(t, logicaltest.TestCase{ logicaltest.Test(t, logicaltest.TestCase{
Backend: testFactory(t), Backend: testFactory(t),
Steps: []logicaltest.TestStep{ Steps: []logicaltest.TestStep{
testAccStepCert(t, "web", ca, "foo", "", "", false), testAccStepCert(t, "web", ca, "foo", allowed{}, false),
testAccStepLogin(t, connState), testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", "example.com", "", false), testAccStepCert(t, "web", ca, "foo", allowed{names: "example.com"}, false),
testAccStepLogin(t, connState), testAccStepLogin(t, connState),
testAccStepCert(t, "web", ca, "foo", "invalid", "", false), testAccStepCert(t, "web", ca, "foo", allowed{names: "invalid"}, false),
testAccStepLoginInvalid(t, connState), 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), testAccStepLoginInvalid(t, connState),
}, },
}) })
} }
// Test a self-signed client with custom extensions (root CA) that is trusted func TestBackend_common_name_singleCert(t *testing.T) {
func TestBackend_extensions_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( connState, err := testConnState(
"test-fixtures/root/rootcawextcert.pem", "test-fixtures/root/rootcawextcert.pem",
"test-fixtures/root/rootcawextkey.pem", "test-fixtures/root/rootcawextkey.pem",
@ -955,39 +980,132 @@ func TestBackend_extensions_singleCert(t *testing.T) {
logicaltest.Test(t, logicaltest.TestCase{ logicaltest.Test(t, logicaltest.TestCase{
Backend: testFactory(t), Backend: testFactory(t),
Steps: []logicaltest.TestStep{ 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), 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), 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), 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), 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), 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), 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), 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), 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), 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), 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), 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), 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), 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), 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), 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), 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), testAccStepLoginInvalid(t, connState),
}, },
}) })
@ -1007,9 +1125,9 @@ func TestBackend_mixed_constraints(t *testing.T) {
logicaltest.Test(t, logicaltest.TestCase{ logicaltest.Test(t, logicaltest.TestCase{
Backend: testFactory(t), Backend: testFactory(t),
Steps: []logicaltest.TestStep{ Steps: []logicaltest.TestStep{
testAccStepCert(t, "1unconstrained", ca, "foo", "", "", false), testAccStepCert(t, "1unconstrained", ca, "foo", allowed{}, false),
testAccStepCert(t, "2matching", ca, "foo", "*.example.com,whatever", "", false), testAccStepCert(t, "2matching", ca, "foo", allowed{names: "*.example.com,whatever"}, false),
testAccStepCert(t, "3invalid", ca, "foo", "invalid", "", false), testAccStepCert(t, "3invalid", ca, "foo", allowed{names: "invalid"}, false),
testAccStepLogin(t, connState), testAccStepLogin(t, connState),
// Assumes CertEntries are processed in alphabetical order (due to store.List), so we only match 2matching if 1unconstrained doesn't match // 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"), testAccStepLoginWithName(t, connState, "2matching"),
@ -1314,8 +1432,17 @@ 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( 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{ return logicaltest.TestStep{
Operation: logical.UpdateOperation, Operation: logical.UpdateOperation,
Path: "certs/" + name, Path: "certs/" + name,
@ -1324,8 +1451,12 @@ func testAccStepCert(
"certificate": string(cert), "certificate": string(cert),
"policies": policies, "policies": policies,
"display_name": name, "display_name": name,
"allowed_names": allowedNames, "allowed_names": testData.names,
"required_extensions": requiredExtensions, "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, "lease": 1000,
}, },
Check: func(resp *logical.Response) error { Check: func(resp *logical.Response) error {

View File

@ -45,7 +45,33 @@ Must be x509 PEM encoded.`,
"allowed_names": &framework.FieldSchema{ "allowed_names": &framework.FieldSchema{
Type: framework.TypeCommaStringSlice, Type: framework.TypeCommaStringSlice,
Description: `A comma-separated list of names. 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{ "required_extensions": &framework.FieldSchema{
@ -77,12 +103,14 @@ seconds. Defaults to system/backend default TTL.`,
Description: `TTL for tokens issued by this backend. Description: `TTL for tokens issued by this backend.
Defaults to system/backend default TTL time.`, Defaults to system/backend default TTL time.`,
}, },
"max_ttl": &framework.FieldSchema{ "max_ttl": &framework.FieldSchema{
Type: framework.TypeDurationSecond, Type: framework.TypeDurationSecond,
Description: `Duration in either an integer number of seconds (3600) or Description: `Duration in either an integer number of seconds (3600) or
an integer time unit (60m) after which the an integer time unit (60m) after which the
issued token can no longer be renewed.`, issued token can no longer be renewed.`,
}, },
"period": &framework.FieldSchema{ "period": &framework.FieldSchema{
Type: framework.TypeDurationSecond, Type: framework.TypeDurationSecond,
Description: `If set, indicates that the token generated using this role Description: `If set, indicates that the token generated using this role
@ -158,6 +186,11 @@ func (b *backend) pathCertRead(ctx context.Context, req *logical.Request, d *fra
"max_ttl": cert.MaxTTL / time.Second, "max_ttl": cert.MaxTTL / time.Second,
"period": cert.Period / time.Second, "period": cert.Period / time.Second,
"allowed_names": cert.AllowedNames, "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 }, nil
} }
@ -168,6 +201,10 @@ func (b *backend) pathCertWrite(ctx context.Context, req *logical.Request, d *fr
displayName := d.Get("display_name").(string) displayName := d.Get("display_name").(string)
policies := policyutil.ParsePolicies(d.Get("policies")) policies := policyutil.ParsePolicies(d.Get("policies"))
allowedNames := d.Get("allowed_names").([]string) 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) requiredExtensions := d.Get("required_extensions").([]string)
var resp logical.Response var resp logical.Response
@ -246,6 +283,10 @@ func (b *backend) pathCertWrite(ctx context.Context, req *logical.Request, d *fr
DisplayName: displayName, DisplayName: displayName,
Policies: policies, Policies: policies,
AllowedNames: allowedNames, AllowedNames: allowedNames,
AllowedCommonNames: allowedCommonNames,
AllowedDNSSANs: allowedDNSSANs,
AllowedEmailSANs: allowedEmailSANs,
AllowedURISANs: allowedURISANs,
RequiredExtensions: requiredExtensions, RequiredExtensions: requiredExtensions,
TTL: ttl, TTL: ttl,
MaxTTL: maxTTL, MaxTTL: maxTTL,
@ -278,6 +319,10 @@ type CertEntry struct {
MaxTTL time.Duration MaxTTL time.Duration
Period time.Duration Period time.Duration
AllowedNames []string AllowedNames []string
AllowedCommonNames []string
AllowedDNSSANs []string
AllowedEmailSANs []string
AllowedURISANs []string
RequiredExtensions []string RequiredExtensions []string
BoundCIDRs []*sockaddr.SockAddrMarshaler 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 { func (b *backend) matchesConstraints(clientCert *x509.Certificate, trustedChain []*x509.Certificate, config *ParsedCert) bool {
return !b.checkForChainInCRLs(trustedChain) && return !b.checkForChainInCRLs(trustedChain) &&
b.matchesNames(clientCert, config) && b.matchesNames(clientCert, config) &&
b.matchesCommonName(clientCert, config) &&
b.matchesDNSSANs(clientCert, config) &&
b.matchesEmailSANs(clientCert, config) &&
b.matchesURISANs(clientCert, config) &&
b.matchesCertificateExtensions(clientCert, config) b.matchesCertificateExtensions(clientCert, config)
} }
@ -280,10 +284,85 @@ func (b *backend) matchesNames(clientCert *x509.Certificate, config *ParsedCert)
return true return true
} }
} }
} }
return false 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 // matchesCertificateExtensions verifies that the certificate matches configured
// required extensions // required extensions
func (b *backend) matchesCertificateExtensions(clientCert *x509.Certificate, config *ParsedCert) bool { 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 CN = example.com
[ req_v3 ] [ req_v3 ]
subjectAltName = @alt_names
2.1.1.1=ASN1:UTF8String:A UTF8String Extension 2.1.1.1=ASN1:UTF8String:A UTF8String Extension
2.1.1.2=ASN1:UTF8:A UTF8 Extension 2.1.1.2=ASN1:UTF8:A UTF8 Extension
2.1.1.3=ASN1:IA5:An IA5 Extension 2.1.1.3=ASN1:IA5:An IA5 Extension
2.1.1.4=ASN1:VISIBLE:A Visible 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 ( import (
"context" "context"
"crypto/x509"
"encoding/pem"
"fmt"
"strings"
"text/template"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/vault/helper/consts" "github.com/hashicorp/vault/helper/consts"
"github.com/hashicorp/vault/helper/ldaputil" "github.com/hashicorp/vault/helper/ldaputil"
"github.com/hashicorp/vault/helper/tlsutil"
"github.com/hashicorp/vault/logical" "github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/logical/framework" "github.com/hashicorp/vault/logical/framework"
) )
@ -19,105 +12,7 @@ import (
func pathConfig(b *backend) *framework.Path { func pathConfig(b *backend) *framework.Path {
return &framework.Path{ return &framework.Path{
Pattern: `config`, Pattern: `config`,
Fields: map[string]*framework.FieldSchema{ Fields: ldaputil.ConfigFields(),
"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.",
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{ Callbacks: map[logical.Operation]framework.OperationFunc{
logical.ReadOperation: b.pathConfigRead, 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 // Create a new ConfigEntry, filling in defaults where appropriate
result, err := b.newConfigEntry(fd) result, err := ldaputil.NewConfigEntry(fd)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -195,147 +90,14 @@ func (b *backend) pathConfigRead(ctx context.Context, req *logical.Request, d *f
} }
resp := &logical.Response{ resp := &logical.Response{
Data: map[string]interface{}{ Data: cfg.PasswordlessMap(),
"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,
},
} }
return resp, nil 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) { 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 // Build a ConfigEntry struct out of the supplied FieldData
cfg, err := b.newConfigEntry(d) cfg, err := ldaputil.NewConfigEntry(d)
if err != nil { if err != nil {
return logical.ErrorResponse(err.Error()), nil return logical.ErrorResponse(err.Error()), nil
} }

View File

@ -66,10 +66,12 @@ func (c *AuthCommand) Run(args []string) int {
for _, arg := range args { for _, arg := range args {
switch { switch {
case strings.HasPrefix(arg, "-methods"): case strings.HasPrefix(arg, "-methods"):
if Format(c.UI) == "table" {
c.UI.Warn(wrapAtLength( c.UI.Warn(wrapAtLength(
"WARNING! The -methods flag is deprecated. Please use "+ "WARNING! The -methods flag is deprecated. Please use "+
"\"vault auth list\" instead. This flag will be removed in "+ "\"vault auth list\" instead. This flag will be removed in "+
"Vault 0.11 (or later).") + "\n") "Vault 0.11 (or later).") + "\n")
}
return (&AuthListCommand{ return (&AuthListCommand{
BaseCommand: &BaseCommand{ BaseCommand: &BaseCommand{
UI: c.UI, UI: c.UI,
@ -77,10 +79,12 @@ func (c *AuthCommand) Run(args []string) int {
}, },
}).Run(nil) }).Run(nil)
case strings.HasPrefix(arg, "-method-help"): case strings.HasPrefix(arg, "-method-help"):
if Format(c.UI) == "table" {
c.UI.Warn(wrapAtLength( c.UI.Warn(wrapAtLength(
"WARNING! The -method-help flag is deprecated. Please use "+ "WARNING! The -method-help flag is deprecated. Please use "+
"\"vault auth help\" instead. This flag will be removed in "+ "\"vault auth help\" instead. This flag will be removed in "+
"Vault 0.11 (or later).") + "\n") "Vault 0.11 (or later).") + "\n")
}
// Parse the args to pull out the method, suppressing any errors because // Parse the args to pull out the method, suppressing any errors because
// there could be other flags that we don't care about. // there could be other flags that we don't care about.
f := flag.NewFlagSet("", flag.ContinueOnError) 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 // 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. // passed directly to the new "vault login" command.
if Format(c.UI) == "table" {
c.UI.Warn(wrapAtLength( c.UI.Warn(wrapAtLength(
"WARNING! The \"vault auth ARG\" command is deprecated and is now a "+ "WARNING! The \"vault auth ARG\" command is deprecated and is now a "+
"subcommand for interacting with auth methods. To authenticate "+ "subcommand for interacting with auth methods. To authenticate "+
"locally to Vault, use \"vault login\" instead. This backwards "+ "locally to Vault, use \"vault login\" instead. This backwards "+
"compatibility will be removed in Vault 0.11 (or later).") + "\n") "compatibility will be removed in Vault 0.11 (or later).") + "\n")
}
return (&LoginCommand{ return (&LoginCommand{
BaseCommand: &BaseCommand{ BaseCommand: &BaseCommand{
UI: c.UI, 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 " + Usage: "Print only the field with the given name. Specifying " +
"this option will take precedence over other formatting " + "this option will take precedence over other formatting " +
"directives. The result will not have a trailing newline " + "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" "os/signal"
"syscall" "syscall"
ad "github.com/hashicorp/vault-plugin-secrets-ad/plugin"
gcp "github.com/hashicorp/vault-plugin-secrets-gcp/plugin" gcp "github.com/hashicorp/vault-plugin-secrets-gcp/plugin"
kv "github.com/hashicorp/vault-plugin-secrets-kv" kv "github.com/hashicorp/vault-plugin-secrets-kv"
"github.com/hashicorp/vault/audit" "github.com/hashicorp/vault/audit"
@ -110,6 +111,7 @@ var (
} }
logicalBackends = map[string]logical.Factory{ logicalBackends = map[string]logical.Factory{
"ad": ad.Factory,
"aws": aws.Factory, "aws": aws.Factory,
"cassandra": cassandra.Factory, "cassandra": cassandra.Factory,
"consul": consul.Factory, "consul": consul.Factory,
@ -211,359 +213,212 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
}, },
} }
Commands = map[string]cli.CommandFactory{ getBaseCommand := func() *BaseCommand {
"audit": func() (cli.Command, error) { return &BaseCommand{
return &AuditCommand{
BaseCommand: &BaseCommand{
UI: ui, UI: ui,
tokenHelper: runOpts.TokenHelper, tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address, flagAddress: runOpts.Address,
}, client: runOpts.Client,
}
}
Commands = map[string]cli.CommandFactory{
"audit": func() (cli.Command, error) {
return &AuditCommand{
BaseCommand: getBaseCommand(),
}, nil }, nil
}, },
"audit disable": func() (cli.Command, error) { "audit disable": func() (cli.Command, error) {
return &AuditDisableCommand{ return &AuditDisableCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, nil }, nil
}, },
"audit enable": func() (cli.Command, error) { "audit enable": func() (cli.Command, error) {
return &AuditEnableCommand{ return &AuditEnableCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, nil }, nil
}, },
"audit list": func() (cli.Command, error) { "audit list": func() (cli.Command, error) {
return &AuditListCommand{ return &AuditListCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, nil }, nil
}, },
"auth tune": func() (cli.Command, error) { "auth tune": func() (cli.Command, error) {
return &AuthTuneCommand{ return &AuthTuneCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, nil }, nil
}, },
"auth": func() (cli.Command, error) { "auth": func() (cli.Command, error) {
return &AuthCommand{ return &AuthCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
Handlers: loginHandlers, Handlers: loginHandlers,
}, nil }, nil
}, },
"auth disable": func() (cli.Command, error) { "auth disable": func() (cli.Command, error) {
return &AuthDisableCommand{ return &AuthDisableCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, nil }, nil
}, },
"auth enable": func() (cli.Command, error) { "auth enable": func() (cli.Command, error) {
return &AuthEnableCommand{ return &AuthEnableCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, nil }, nil
}, },
"auth help": func() (cli.Command, error) { "auth help": func() (cli.Command, error) {
return &AuthHelpCommand{ return &AuthHelpCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
Handlers: loginHandlers, Handlers: loginHandlers,
}, nil }, nil
}, },
"auth list": func() (cli.Command, error) { "auth list": func() (cli.Command, error) {
return &AuthListCommand{ return &AuthListCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, nil }, nil
}, },
"delete": func() (cli.Command, error) { "delete": func() (cli.Command, error) {
return &DeleteCommand{ return &DeleteCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, nil }, nil
}, },
"lease": func() (cli.Command, error) { "lease": func() (cli.Command, error) {
return &LeaseCommand{ return &LeaseCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, nil }, nil
}, },
"lease renew": func() (cli.Command, error) { "lease renew": func() (cli.Command, error) {
return &LeaseRenewCommand{ return &LeaseRenewCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, nil }, nil
}, },
"lease revoke": func() (cli.Command, error) { "lease revoke": func() (cli.Command, error) {
return &LeaseRevokeCommand{ return &LeaseRevokeCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, nil }, nil
}, },
"list": func() (cli.Command, error) { "list": func() (cli.Command, error) {
return &ListCommand{ return &ListCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, nil }, nil
}, },
"login": func() (cli.Command, error) { "login": func() (cli.Command, error) {
return &LoginCommand{ return &LoginCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
Handlers: loginHandlers, Handlers: loginHandlers,
}, nil }, nil
}, },
"operator": func() (cli.Command, error) { "operator": func() (cli.Command, error) {
return &OperatorCommand{ return &OperatorCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, nil }, nil
}, },
"operator generate-root": func() (cli.Command, error) { "operator generate-root": func() (cli.Command, error) {
return &OperatorGenerateRootCommand{ return &OperatorGenerateRootCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, nil }, nil
}, },
"operator init": func() (cli.Command, error) { "operator init": func() (cli.Command, error) {
return &OperatorInitCommand{ return &OperatorInitCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, nil }, nil
}, },
"operator key-status": func() (cli.Command, error) { "operator key-status": func() (cli.Command, error) {
return &OperatorKeyStatusCommand{ return &OperatorKeyStatusCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, nil }, nil
}, },
"operator rekey": func() (cli.Command, error) { "operator rekey": func() (cli.Command, error) {
return &OperatorRekeyCommand{ return &OperatorRekeyCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, nil }, nil
}, },
"operator rotate": func() (cli.Command, error) { "operator rotate": func() (cli.Command, error) {
return &OperatorRotateCommand{ return &OperatorRotateCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, nil }, nil
}, },
"operator seal": func() (cli.Command, error) { "operator seal": func() (cli.Command, error) {
return &OperatorSealCommand{ return &OperatorSealCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, nil }, nil
}, },
"operator step-down": func() (cli.Command, error) { "operator step-down": func() (cli.Command, error) {
return &OperatorStepDownCommand{ return &OperatorStepDownCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, nil }, nil
}, },
"operator unseal": func() (cli.Command, error) { "operator unseal": func() (cli.Command, error) {
return &OperatorUnsealCommand{ return &OperatorUnsealCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, nil }, nil
}, },
"path-help": func() (cli.Command, error) { "path-help": func() (cli.Command, error) {
return &PathHelpCommand{ return &PathHelpCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, nil }, nil
}, },
"policy": func() (cli.Command, error) { "policy": func() (cli.Command, error) {
return &PolicyCommand{ return &PolicyCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, nil }, nil
}, },
"policy delete": func() (cli.Command, error) { "policy delete": func() (cli.Command, error) {
return &PolicyDeleteCommand{ return &PolicyDeleteCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, nil }, nil
}, },
"policy fmt": func() (cli.Command, error) { "policy fmt": func() (cli.Command, error) {
return &PolicyFmtCommand{ return &PolicyFmtCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, nil }, nil
}, },
"policy list": func() (cli.Command, error) { "policy list": func() (cli.Command, error) {
return &PolicyListCommand{ return &PolicyListCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, nil }, nil
}, },
"policy read": func() (cli.Command, error) { "policy read": func() (cli.Command, error) {
return &PolicyReadCommand{ return &PolicyReadCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, nil }, nil
}, },
"policy write": func() (cli.Command, error) { "policy write": func() (cli.Command, error) {
return &PolicyWriteCommand{ return &PolicyWriteCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, nil }, nil
}, },
"read": func() (cli.Command, error) { "read": func() (cli.Command, error) {
return &ReadCommand{ return &ReadCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, nil }, nil
}, },
"secrets": func() (cli.Command, error) { "secrets": func() (cli.Command, error) {
return &SecretsCommand{ return &SecretsCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, nil }, nil
}, },
"secrets disable": func() (cli.Command, error) { "secrets disable": func() (cli.Command, error) {
return &SecretsDisableCommand{ return &SecretsDisableCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, nil }, nil
}, },
"secrets enable": func() (cli.Command, error) { "secrets enable": func() (cli.Command, error) {
return &SecretsEnableCommand{ return &SecretsEnableCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, nil }, nil
}, },
"secrets list": func() (cli.Command, error) { "secrets list": func() (cli.Command, error) {
return &SecretsListCommand{ return &SecretsListCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, nil }, nil
}, },
"secrets move": func() (cli.Command, error) { "secrets move": func() (cli.Command, error) {
return &SecretsMoveCommand{ return &SecretsMoveCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, nil }, nil
}, },
"secrets tune": func() (cli.Command, error) { "secrets tune": func() (cli.Command, error) {
return &SecretsTuneCommand{ return &SecretsTuneCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, nil }, nil
}, },
"server": func() (cli.Command, error) { "server": func() (cli.Command, error) {
@ -583,193 +438,123 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
}, },
"ssh": func() (cli.Command, error) { "ssh": func() (cli.Command, error) {
return &SSHCommand{ return &SSHCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, nil }, nil
}, },
"status": func() (cli.Command, error) { "status": func() (cli.Command, error) {
return &StatusCommand{ return &StatusCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, nil }, nil
}, },
"token": func() (cli.Command, error) { "token": func() (cli.Command, error) {
return &TokenCommand{ return &TokenCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, nil }, nil
}, },
"token create": func() (cli.Command, error) { "token create": func() (cli.Command, error) {
return &TokenCreateCommand{ return &TokenCreateCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, nil }, nil
}, },
"token capabilities": func() (cli.Command, error) { "token capabilities": func() (cli.Command, error) {
return &TokenCapabilitiesCommand{ return &TokenCapabilitiesCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, nil }, nil
}, },
"token lookup": func() (cli.Command, error) { "token lookup": func() (cli.Command, error) {
return &TokenLookupCommand{ return &TokenLookupCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, nil }, nil
}, },
"token renew": func() (cli.Command, error) { "token renew": func() (cli.Command, error) {
return &TokenRenewCommand{ return &TokenRenewCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, nil }, nil
}, },
"token revoke": func() (cli.Command, error) { "token revoke": func() (cli.Command, error) {
return &TokenRevokeCommand{ return &TokenRevokeCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, nil }, nil
}, },
"unwrap": func() (cli.Command, error) { "unwrap": func() (cli.Command, error) {
return &UnwrapCommand{ return &UnwrapCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, nil }, nil
}, },
"version": func() (cli.Command, error) { "version": func() (cli.Command, error) {
return &VersionCommand{ return &VersionCommand{
VersionInfo: version.GetVersion(), VersionInfo: version.GetVersion(),
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, nil }, nil
}, },
"write": func() (cli.Command, error) { "write": func() (cli.Command, error) {
return &WriteCommand{ return &WriteCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, nil }, nil
}, },
"kv": func() (cli.Command, error) { "kv": func() (cli.Command, error) {
return &KVCommand{ return &KVCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
},
}, nil }, nil
}, },
"kv put": func() (cli.Command, error) { "kv put": func() (cli.Command, error) {
return &KVPutCommand{ return &KVPutCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
},
}, nil }, nil
}, },
"kv patch": func() (cli.Command, error) { "kv patch": func() (cli.Command, error) {
return &KVPatchCommand{ return &KVPatchCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
},
}, nil }, nil
}, },
"kv get": func() (cli.Command, error) { "kv get": func() (cli.Command, error) {
return &KVGetCommand{ return &KVGetCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
},
}, nil }, nil
}, },
"kv delete": func() (cli.Command, error) { "kv delete": func() (cli.Command, error) {
return &KVDeleteCommand{ return &KVDeleteCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
},
}, nil }, nil
}, },
"kv list": func() (cli.Command, error) { "kv list": func() (cli.Command, error) {
return &KVListCommand{ return &KVListCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
},
}, nil }, nil
}, },
"kv destroy": func() (cli.Command, error) { "kv destroy": func() (cli.Command, error) {
return &KVDestroyCommand{ return &KVDestroyCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
},
}, nil }, nil
}, },
"kv undelete": func() (cli.Command, error) { "kv undelete": func() (cli.Command, error) {
return &KVUndeleteCommand{ return &KVUndeleteCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
},
}, nil }, nil
}, },
"kv enable-versioning": func() (cli.Command, error) { "kv enable-versioning": func() (cli.Command, error) {
return &KVEnableVersioningCommand{ return &KVEnableVersioningCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
},
}, nil }, nil
}, },
"kv metadata": func() (cli.Command, error) { "kv metadata": func() (cli.Command, error) {
return &KVMetadataCommand{ return &KVMetadataCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
},
}, nil }, nil
}, },
"kv metadata put": func() (cli.Command, error) { "kv metadata put": func() (cli.Command, error) {
return &KVMetadataPutCommand{ return &KVMetadataPutCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
},
}, nil }, nil
}, },
"kv metadata get": func() (cli.Command, error) { "kv metadata get": func() (cli.Command, error) {
return &KVMetadataGetCommand{ return &KVMetadataGetCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
},
}, nil }, nil
}, },
"kv metadata delete": func() (cli.Command, error) { "kv metadata delete": func() (cli.Command, error) {
return &KVMetadataDeleteCommand{ return &KVMetadataDeleteCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
},
}, nil }, nil
}, },
} }
@ -782,12 +567,9 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
return &DeprecatedCommand{ return &DeprecatedCommand{
Old: "audit-disable", Old: "audit-disable",
New: "audit disable", New: "audit disable",
Command: &AuditDisableCommand{
BaseCommand: &BaseCommand{
UI: ui, UI: ui,
tokenHelper: runOpts.TokenHelper, Command: &AuditDisableCommand{
flagAddress: runOpts.Address, BaseCommand: getBaseCommand(),
},
}, },
}, nil }, nil
}, },
@ -798,11 +580,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "audit enable", New: "audit enable",
UI: ui, UI: ui,
Command: &AuditEnableCommand{ Command: &AuditEnableCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, },
}, nil }, nil
}, },
@ -813,11 +591,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "audit list", New: "audit list",
UI: ui, UI: ui,
Command: &AuditListCommand{ Command: &AuditListCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, },
}, nil }, nil
}, },
@ -828,11 +602,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "auth disable", New: "auth disable",
UI: ui, UI: ui,
Command: &AuthDisableCommand{ Command: &AuthDisableCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, },
}, nil }, nil
}, },
@ -843,11 +613,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "auth enable", New: "auth enable",
UI: ui, UI: ui,
Command: &AuthEnableCommand{ Command: &AuthEnableCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, },
}, nil }, nil
}, },
@ -858,11 +624,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "token capabilities", New: "token capabilities",
UI: ui, UI: ui,
Command: &TokenCapabilitiesCommand{ Command: &TokenCapabilitiesCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, },
}, nil }, nil
}, },
@ -873,11 +635,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "operator generate-root", New: "operator generate-root",
UI: ui, UI: ui,
Command: &OperatorGenerateRootCommand{ Command: &OperatorGenerateRootCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, },
}, nil }, nil
}, },
@ -888,11 +646,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "operator init", New: "operator init",
UI: ui, UI: ui,
Command: &OperatorInitCommand{ Command: &OperatorInitCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, },
}, nil }, nil
}, },
@ -903,11 +657,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "operator key-status", New: "operator key-status",
UI: ui, UI: ui,
Command: &OperatorKeyStatusCommand{ Command: &OperatorKeyStatusCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, },
}, nil }, nil
}, },
@ -918,11 +668,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "lease renew", New: "lease renew",
UI: ui, UI: ui,
Command: &LeaseRenewCommand{ Command: &LeaseRenewCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, },
}, nil }, nil
}, },
@ -933,11 +679,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "lease revoke", New: "lease revoke",
UI: ui, UI: ui,
Command: &LeaseRevokeCommand{ Command: &LeaseRevokeCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, },
}, nil }, nil
}, },
@ -948,11 +690,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "secrets enable", New: "secrets enable",
UI: ui, UI: ui,
Command: &SecretsEnableCommand{ Command: &SecretsEnableCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, },
}, nil }, nil
}, },
@ -963,11 +701,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "secrets tune", New: "secrets tune",
UI: ui, UI: ui,
Command: &SecretsTuneCommand{ Command: &SecretsTuneCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, },
}, nil }, nil
}, },
@ -978,11 +712,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "secrets list", New: "secrets list",
UI: ui, UI: ui,
Command: &SecretsListCommand{ Command: &SecretsListCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, },
}, nil }, nil
}, },
@ -993,11 +723,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "policy read\" or \"vault policy list", // lol New: "policy read\" or \"vault policy list", // lol
UI: ui, UI: ui,
Command: &PoliciesDeprecatedCommand{ Command: &PoliciesDeprecatedCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, },
}, nil }, nil
}, },
@ -1008,11 +734,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "policy delete", New: "policy delete",
UI: ui, UI: ui,
Command: &PolicyDeleteCommand{ Command: &PolicyDeleteCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, },
}, nil }, nil
}, },
@ -1023,11 +745,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "policy write", New: "policy write",
UI: ui, UI: ui,
Command: &PolicyWriteCommand{ Command: &PolicyWriteCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, },
}, nil }, nil
}, },
@ -1038,11 +756,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "operator rekey", New: "operator rekey",
UI: ui, UI: ui,
Command: &OperatorRekeyCommand{ Command: &OperatorRekeyCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, },
}, nil }, nil
}, },
@ -1053,11 +767,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "secrets move", New: "secrets move",
UI: ui, UI: ui,
Command: &SecretsMoveCommand{ Command: &SecretsMoveCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, },
}, nil }, nil
}, },
@ -1068,11 +778,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "operator rotate", New: "operator rotate",
UI: ui, UI: ui,
Command: &OperatorRotateCommand{ Command: &OperatorRotateCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, },
}, nil }, nil
}, },
@ -1083,11 +789,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "operator seal", New: "operator seal",
UI: ui, UI: ui,
Command: &OperatorSealCommand{ Command: &OperatorSealCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, },
}, nil }, nil
}, },
@ -1098,11 +800,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "operator step-down", New: "operator step-down",
UI: ui, UI: ui,
Command: &OperatorStepDownCommand{ Command: &OperatorStepDownCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, },
}, nil }, nil
}, },
@ -1113,11 +811,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "token create", New: "token create",
UI: ui, UI: ui,
Command: &TokenCreateCommand{ Command: &TokenCreateCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, },
}, nil }, nil
}, },
@ -1128,11 +822,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "token lookup", New: "token lookup",
UI: ui, UI: ui,
Command: &TokenLookupCommand{ Command: &TokenLookupCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, },
}, nil }, nil
}, },
@ -1143,11 +833,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "token renew", New: "token renew",
UI: ui, UI: ui,
Command: &TokenRenewCommand{ Command: &TokenRenewCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, },
}, nil }, nil
}, },
@ -1158,11 +844,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "token revoke", New: "token revoke",
UI: ui, UI: ui,
Command: &TokenRevokeCommand{ Command: &TokenRevokeCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, },
}, nil }, nil
}, },
@ -1173,11 +855,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "secrets disable", New: "secrets disable",
UI: ui, UI: ui,
Command: &SecretsDisableCommand{ Command: &SecretsDisableCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, },
}, nil }, nil
}, },
@ -1188,11 +866,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
New: "operator unseal", New: "operator unseal",
UI: ui, UI: ui,
Command: &OperatorUnsealCommand{ Command: &OperatorUnsealCommand{
BaseCommand: &BaseCommand{ BaseCommand: getBaseCommand(),
UI: ui,
tokenHelper: runOpts.TokenHelper,
flagAddress: runOpts.Address,
},
}, },
}, nil }, nil
}, },

View File

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

View File

@ -1,6 +1,7 @@
package command package command
import ( import (
"bytes"
"os" "os"
"strings" "strings"
"testing" "testing"
@ -91,13 +92,13 @@ func Test_Format_Parsing(t *testing.T) {
}{ }{
{ {
"format", "format",
[]string{"-format", "json"}, []string{"token", "renew", "-format", "json"},
"{", "{",
0, 0,
}, },
{ {
"format_bad", "format_bad",
[]string{"-format", "nope-not-real"}, []string{"token", "renew", "-format", "nope-not-real"},
"Invalid output format", "Invalid output format",
1, 1,
}, },
@ -110,21 +111,24 @@ func Test_Format_Parsing(t *testing.T) {
client, closer := testVaultServer(t) client, closer := testVaultServer(t)
defer closer() 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. // Login with the token so we can renew-self.
token, _ := testTokenAndAccessor(t, client) token, _ := testTokenAndAccessor(t, client)
client.SetToken(token) client.SetToken(token)
ui, cmd := testTokenRenewCommand(t) code := RunCustom(tc.args, runOpts)
cmd.client = client
tc.args = setupEnv(tc.args)
code := cmd.Run(tc.args)
if code != tc.code { if code != tc.code {
t.Errorf("expected %d to be %d", 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) { if !strings.Contains(combined, tc.out) {
t.Errorf("expected %q to contain %q", 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: case 2:
// Deprecation // Deprecation
// TODO: remove in 0.9.0 // TODO: remove in 0.9.0
if Format(c.UI) == "table" {
c.UI.Warn(wrapAtLength( c.UI.Warn(wrapAtLength(
"WARNING! Specifying INCREMENT as a second argument is deprecated. " + "WARNING! Specifying INCREMENT as a second argument is deprecated. " +
"Please use -increment instead. This will be removed in the next " + "Please use -increment instead. This will be removed in the next " +
"major release of Vault.")) "major release of Vault."))
}
leaseID = strings.TrimSpace(args[0]) leaseID = strings.TrimSpace(args[0])
parsed, err := time.ParseDuration(appendDurationSuffix(args[1])) parsed, err := time.ParseDuration(appendDurationSuffix(args[1]))
if err != nil { if err != nil {

View File

@ -167,6 +167,7 @@ func (c *LoginCommand) Run(args []string) int {
// TODO: remove in 0.10.0 // TODO: remove in 0.10.0
switch { switch {
case c.flagNoVerify: case c.flagNoVerify:
if Format(c.UI) == "table" {
c.UI.Warn(wrapAtLength( c.UI.Warn(wrapAtLength(
"WARNING! The -no-verify flag is deprecated. In the past, Vault " + "WARNING! The -no-verify flag is deprecated. In the past, Vault " +
"performed a lookup on a token after authentication. This is no " + "performed a lookup on a token after authentication. This is no " +
@ -187,6 +188,7 @@ func (c *LoginCommand) Run(args []string) int {
"If you are not using token authentication, you can safely omit this " + "If you are not using token authentication, you can safely omit this " +
"flag. Vault will not perform a lookup after authentication.")) "flag. Vault will not perform a lookup after authentication."))
c.UI.Warn("") c.UI.Warn("")
}
// There's no point in passing this to other auth handlers... // There's no point in passing this to other auth handlers...
if c.flagMethod == "token" { if c.flagMethod == "token" {

View File

@ -10,6 +10,7 @@ import (
"text/tabwriter" "text/tabwriter"
"github.com/fatih/color" "github.com/fatih/color"
"github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/command/token" "github.com/hashicorp/vault/command/token"
colorable "github.com/mattn/go-colorable" colorable "github.com/mattn/go-colorable"
"github.com/mitchellh/cli" "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 // setupEnv parses args and may replace them and sets some env vars to known
// values based on format options // values based on format options
func setupEnv(args []string) []string { func setupEnv(args []string) (retArgs []string, format string) {
var format string
var nextArgFormat bool var nextArgFormat bool
for _, arg := range args { for _, arg := range args {
@ -65,10 +65,8 @@ func setupEnv(args []string) []string {
if format == "" { if format == "" {
format = "table" format = "table"
} }
// Put back into the env for later
os.Setenv(EnvVaultFormat, format)
return args return args, format
} }
type RunOptions struct { type RunOptions struct {
@ -76,6 +74,7 @@ type RunOptions struct {
Stdout io.Writer Stdout io.Writer
Stderr io.Writer Stderr io.Writer
Address string Address string
Client *api.Client
} }
func Run(args []string) int { func Run(args []string) int {
@ -89,7 +88,8 @@ func RunCustom(args []string, runOpts *RunOptions) int {
runOpts = &RunOptions{} runOpts = &RunOptions{}
} }
args = setupEnv(args) var format string
args, format = setupEnv(args)
// Don't use color if disabled // Don't use color if disabled
useColor := true useColor := true
@ -104,8 +104,6 @@ func RunCustom(args []string, runOpts *RunOptions) int {
runOpts.Stderr = os.Stderr runOpts.Stderr = os.Stderr
} }
format := format()
// Only use colored UI if stdout is a tty, and not disabled // Only use colored UI if stdout is a tty, and not disabled
if useColor && format == "table" { if useColor && format == "table" {
if f, ok := runOpts.Stdout.(*os.File); ok { 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 // TODO: remove in 0.9.0
switch { switch {
case c.flagGenOTP: case c.flagGenOTP:
if Format(c.UI) == "table" {
c.UI.Warn(wrapAtLength( c.UI.Warn(wrapAtLength(
"The -gen-otp flag is deprecated. Please use the -generate-otp flag " + "WARNING! The -gen-otp flag is deprecated. Please use the -generate-otp flag " +
"instead.")) "instead."))
}
c.flagGenerateOTP = c.flagGenOTP c.flagGenerateOTP = c.flagGenOTP
} }

View File

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

View File

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

View File

@ -1,6 +1,7 @@
package command package command
import ( import (
"bytes"
"encoding/json" "encoding/json"
"io/ioutil" "io/ioutil"
"os" "os"
@ -147,7 +148,6 @@ func TestOperatorUnsealCommand_Run(t *testing.T) {
func TestOperatorUnsealCommand_Format(t *testing.T) { func TestOperatorUnsealCommand_Format(t *testing.T) {
defer func() { defer func() {
os.Setenv(EnvVaultFormat, "")
os.Setenv(EnvVaultCLINoColor, "") os.Setenv(EnvVaultCLINoColor, "")
}() }()
@ -159,21 +159,28 @@ func TestOperatorUnsealCommand_Format(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
ui, cmd := testOperatorUnsealCommand(t) stdout := bytes.NewBuffer(nil)
cmd.client = client stderr := bytes.NewBuffer(nil)
cmd.testOutput = ioutil.Discard runOpts := &RunOptions{
Stdout: stdout,
args := setupEnv([]string{"-format", "json"}) Stderr: stderr,
Client: client,
// 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())
} }
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") t.Error("expected output to be valid JSON")
} }
} }

View File

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

View File

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

View File

@ -5,10 +5,235 @@ import (
"encoding/pem" "encoding/pem"
"errors" "errors"
"fmt" "fmt"
"strings"
"text/template"
"github.com/hashicorp/vault/helper/tlsutil" "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 { type ConfigEntry struct {
Url string `json:"url"` Url string `json:"url"`
UserDN string `json:"userdn"` UserDN string `json:"userdn"`
@ -34,6 +259,36 @@ type ConfigEntry struct {
CaseSensitiveNames *bool `json:"CaseSensitiveNames,omitempty"` 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 { func (c *ConfigEntry) Validate() error {
if len(c.Url) == 0 { if len(c.Url) == 0 {
return errors.New("at least one url must be provided") return errors.New("at least one url must be provided")

View File

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

View File

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

View File

@ -32,8 +32,12 @@ govendor init
echo "Fetching deps, will take some time..." echo "Fetching deps, will take some time..."
govendor fetch +missing govendor fetch +missing
# Clean up after the logrus mess
govendor remove github.com/Sirupsen/logrus govendor remove github.com/Sirupsen/logrus
cd vendor cd vendor
find -type f | grep '.go' | xargs sed -i -e 's/Sirupsen/sirupsen/' 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" 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}} class={{buttonClasses}}
type="button" type="button"
disabled={{disabled}} disabled={{disabled}}
data-test-confirm-action-trigger=true
{{action 'toggleConfirm'}} {{action 'toggleConfirm'}}
> >
{{yield}} {{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 { task } from 'ember-concurrency';
import { humanize } from 'vault/helpers/humanize'; import { humanize } from 'vault/helpers/humanize';
const { computed } = Ember; const { computed, inject } = Ember;
export default Ember.Component.extend({ export default Ember.Component.extend({
flashMessages: inject.service(),
'data-test-component': 'identity-edit-form',
model: null, model: null,
// 'create', 'edit', 'merge'
mode: 'create', mode: 'create',
/* /*
* @param Function * @param Function
* @public * @public
* *
* Optional param to call a function upon successfully mounting a backend * Optional param to call a function upon successfully saving an entity
*
*/ */
onSave: () => {}, onSave: () => {},
cancelLink: computed('mode', 'model', function() { cancelLink: computed('mode', 'model.identityType', function() {
let { model, mode } = this.getProperties('model', 'mode'); let { model, mode } = this.getProperties('model', 'mode');
let key = `${mode}-${model.get('identityType')}`; let key = `${mode}-${model.get('identityType')}`;
let routes = { let routes = {
@ -33,16 +36,17 @@ export default Ember.Component.extend({
return routes[key]; return routes[key];
}), }),
getMessage(model) { getMessage(model, isDelete = false) {
let mode = this.get('mode'); let mode = this.get('mode');
let typeDisplay = humanize([model.get('identityType')]); let typeDisplay = humanize([model.get('identityType')]);
let action = isDelete ? 'deleted' : 'saved';
if (mode === 'merge') { if (mode === 'merge') {
return 'Successfully merged entities'; return 'Successfully merged entities';
} }
if (model.get('id')) { 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*() { save: task(function*() {
@ -56,13 +60,26 @@ export default Ember.Component.extend({
return; return;
} }
this.get('flashMessages').success(message); this.get('flashMessages').success(message);
yield this.get('onSave')(model); yield this.get('onSave')({saveType: 'save', model});
}).drop(), }).drop(),
willDestroy() { willDestroy() {
let model = this.get('model'); let model = this.get('model');
if (!model.isDestroyed || !model.isDestroying) { if ((model.get('isDirty') && !model.isDestroyed) || !model.isDestroying) {
model.rollbackAttributes(); 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'; import Ember from 'ember';
export default Ember.Component.extend({ export default Ember.Component.extend({
'data-test-component': 'info-table-row',
classNames: ['info-table-row'], classNames: ['info-table-row'],
isVisible: Ember.computed.or('alwaysRender', 'value'), isVisible: Ember.computed.or('alwaysRender', 'value'),

View File

@ -6,6 +6,8 @@ const { computed } = Ember;
export default Ember.Component.extend({ export default Ember.Component.extend({
type: null, type: null,
yieldWithoutColumn: false,
classNameBindings: ['containerClass'], classNameBindings: ['containerClass'],
containerClass: computed('type', function() { 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) { handleSuccess(resp, action) {
let props = {}; let props = {};
if (resp && resp.data && action === 'unwrap') { let secret = (resp && resp.data) || resp.auth;
props = Ember.assign({}, props, { unwrap_data: resp.data }); 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) { if (resp && resp.wrap_info) {
const keyName = action === 'rewrap' ? 'rewrap_token' : 'token'; const keyName = action === 'rewrap' ? 'rewrap_token' : 'token';

View File

@ -1,4 +1,10 @@
import Ember from 'ember'; import Ember from 'ember';
import ListController from 'vault/mixins/list-controller'; 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({ export default Ember.Controller.extend({
showRoute: 'vault.cluster.access.identity.show', showRoute: 'vault.cluster.access.identity.show',
showTab: 'details', showTab: 'details',
navToShow: task(function*(model) { navAfterSave: task(function*({saveType, model}) {
yield this.transitionToRoute(this.get('showRoute'), model.id, this.get('showTab')); 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 Ember from 'ember';
import ListController from 'vault/mixins/list-controller'; 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 Ember from 'ember';
import utils from 'vault/lib/key-utils'; import utils from 'vault/lib/key-utils';
export default Ember.Controller.extend({ const { inject, computed, Controller } = Ember;
flashMessages: Ember.inject.service(), export default Controller.extend({
clusterController: Ember.inject.controller('vault.cluster'), flashMessages: inject.service(),
store: inject.service(),
clusterController: inject.controller('vault.cluster'),
queryParams: { queryParams: {
page: 'page', page: 'page',
pageFilter: 'pageFilter', pageFilter: 'pageFilter',
@ -13,7 +15,7 @@ export default Ember.Controller.extend({
pageFilter: null, pageFilter: null,
filter: null, filter: null,
backendCrumb: Ember.computed(function() { backendCrumb: computed(function() {
return { return {
label: 'leases', label: 'leases',
text: 'leases', text: 'leases',
@ -24,13 +26,13 @@ export default Ember.Controller.extend({
isLoading: false, isLoading: false,
filterMatchesKey: Ember.computed('filter', 'model', 'model.[]', function() { filterMatchesKey: computed('filter', 'model', 'model.[]', function() {
var filter = this.get('filter'); var filter = this.get('filter');
var content = this.get('model'); var content = this.get('model');
return !!(content.length && content.findBy('id', filter)); 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 filter = this.get('filter');
var content = this.get('model'); var content = this.get('model');
var filterMatchesKey = this.get('filterMatchesKey'); 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')); return !!utils.keyIsFolder(this.get('filter'));
}), }),
@ -56,7 +58,7 @@ export default Ember.Controller.extend({
}, },
revokePrefix(prefix, isForce) { revokePrefix(prefix, isForce) {
const adapter = this.model.store.adapterFor('lease'); const adapter = this.get('store').adapterFor('lease');
const method = isForce ? 'forceRevokePrefix' : 'revokePrefix'; const method = isForce ? 'forceRevokePrefix' : 'revokePrefix';
const fn = adapter[method]; const fn = adapter[method];
fn fn

View File

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

View File

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

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 IdentityModel from './_base';
import DS from 'ember-data'; import DS from 'ember-data';
import Ember from 'ember';
import identityCapabilities from 'vault/macros/identity-capabilities';
const { attr, belongsTo } = DS; const { attr, belongsTo } = DS;
const { computed } = Ember;
export default IdentityModel.extend({ export default IdentityModel.extend({
parentType: 'entity',
formFields: ['name', 'mountAccessor', 'metadata'], formFields: ['name', 'mountAccessor', 'metadata'],
entity: belongsTo('identity/entity', { readOnly: true, async: false }), entity: belongsTo('identity/entity', { readOnly: true, async: false }),
@ -12,7 +16,7 @@ export default IdentityModel.extend({
label: 'Auth Backend', label: 'Auth Backend',
editType: 'mountAccessor', editType: 'mountAccessor',
}), }),
metadata: attr('object', { metadata: attr({
editType: 'kv', editType: 'kv',
}), }),
mountPath: attr('string', { mountPath: attr('string', {
@ -28,4 +32,8 @@ export default IdentityModel.extend({
readOnly: true, readOnly: true,
}), }),
mergedFromCanonicalIds: attr(), 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 IdentityModel from './_base';
import DS from 'ember-data'; 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; const { attr, hasMany } = DS;
export default IdentityModel.extend({ export default IdentityModel.extend({
formFields: ['name', 'policies', 'metadata'], formFields: ['name', 'disabled', 'policies', 'metadata'],
name: attr('string'), name: attr('string'),
disabled: attr('boolean', {
defaultValue: false,
label: 'Disable entity',
helpText: 'All associated tokens cannot be used, but are not revoked.',
}),
mergedEntityIds: attr(), mergedEntityIds: attr(),
metadata: attr('object', { metadata: attr({
editType: 'kv', editType: 'kv',
}), }),
policies: attr({ policies: attr({
@ -28,4 +39,11 @@ export default IdentityModel.extend({
inheritedGroupIds: attr({ inheritedGroupIds: attr({
readOnly: true, 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 IdentityModel from './_base';
import DS from 'ember-data'; import DS from 'ember-data';
import Ember from 'ember';
import identityCapabilities from 'vault/macros/identity-capabilities';
const { attr, belongsTo } = DS; const { attr, belongsTo } = DS;
const { computed } = Ember;
export default IdentityModel.extend({ export default IdentityModel.extend({
parentType: 'group',
formFields: ['name', 'mountAccessor'], formFields: ['name', 'mountAccessor'],
group: belongsTo('identity/group', { readOnly: true, async: false }), group: belongsTo('identity/group', { readOnly: true, async: false }),
@ -26,4 +31,9 @@ export default IdentityModel.extend({
lastUpdateTime: attr('string', { lastUpdateTime: attr('string', {
readOnly: true, 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 Ember from 'ember';
import IdentityModel from './_base'; import IdentityModel from './_base';
import DS from 'ember-data'; import DS from 'ember-data';
import lazyCapabilities, { apiPath } from 'vault/macros/lazy-capabilities';
import identityCapabilities from 'vault/macros/identity-capabilities';
const { computed } = Ember; const { computed } = Ember;
const { attr, belongsTo } = DS; const { attr, belongsTo } = DS;
@ -52,4 +54,18 @@ export default IdentityModel.extend({
), ),
alias: belongsTo('identity/group-alias', { async: false, readOnly: true }), 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'; import Fragment from 'ember-data-model-fragments/fragment';
export default Fragment.extend({ 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 { queryRecord } from 'ember-computed-query';
import { fragment } from 'ember-data-model-fragments/attributes'; import { fragment } from 'ember-data-model-fragments/attributes';
import { expandAttributeMeta } from 'vault/utils/field-to-attrs';
const { attr } = DS; const { attr } = DS;
const { computed } = Ember; const { computed } = Ember;
@ -16,11 +18,26 @@ export default DS.Model.extend({
name: attr('string'), name: attr('string'),
type: attr('string'), type: attr('string'),
description: attr('string'), description: attr('string'),
config: attr('object'), config: fragment('mount-config', { defaultValue: {} }),
options: fragment('mount-options'), options: fragment('mount-options', { defaultValue: {} }),
local: attr('boolean'), local: attr('boolean'),
sealWrap: 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() { shouldIncludeInList: computed('type', function() {
return !LIST_EXCLUDED_BACKENDS.includes(this.get('type')); return !LIST_EXCLUDED_BACKENDS.includes(this.get('type'));
}), }),

View File

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

View File

@ -1,6 +1,8 @@
import Ember from 'ember'; 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) { model(params) {
let itemType = this.modelFor('vault.cluster.access.identity'); let itemType = this.modelFor('vault.cluster.access.identity');
let modelType = `identity/${itemType}-alias`; let modelType = `identity/${itemType}-alias`;

View File

@ -1,6 +1,8 @@
import Ember from 'ember'; 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) { model(params) {
let itemType = this.modelFor('vault.cluster.access.identity'); let itemType = this.modelFor('vault.cluster.access.identity');
let modelType = `identity/${itemType}-alias`; let modelType = `identity/${itemType}-alias`;

View File

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

View File

@ -1,6 +1,8 @@
import Ember from 'ember'; 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() { model() {
let itemType = this.modelFor('vault.cluster.access.identity'); let itemType = this.modelFor('vault.cluster.access.identity');
let modelType = `identity/${itemType}`; let modelType = `identity/${itemType}`;

View File

@ -1,6 +1,8 @@
import Ember from 'ember'; 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) { model(params) {
let itemType = this.modelFor('vault.cluster.access.identity'); let itemType = this.modelFor('vault.cluster.access.identity');
let modelType = `identity/${itemType}`; let modelType = `identity/${itemType}`;

View File

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

View File

@ -13,13 +13,36 @@ export default Ember.Route.extend({
Ember.set(error, 'httpStatus', 404); Ember.set(error, 'httpStatus', 404);
throw error; 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({ return Ember.RSVP.hash({
model: this.store.findRecord(modelType, params.item_id), model,
section, 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) { afterModel(resolvedModel) {
let { section, model } = resolvedModel; let { section, model } = resolvedModel;
if (model.get('identityType') === 'group' && model.get('type') === 'internal' && section === 'aliases') { 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; 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 // pushes records into the store and returns the result
fetchPage(modelName, query) { fetchPage(modelName, query) {
const response = this.constructResponse(modelName, query); const response = this.constructResponse(modelName, query);
this.unloadAll(modelName); this.peekAll(modelName).forEach(record => {
record.unloadRecord();
});
return new Ember.RSVP.Promise(resolve => {
Ember.run.schedule('destroy', () => {
this.push( this.push(
this.serializerFor(modelName).normalizeResponse(this, this.modelFor(modelName), response, null, 'query') this.serializerFor(modelName).normalizeResponse(
this,
this.modelFor(modelName),
response,
null,
'query'
)
); );
const model = this.peekAll(modelName); let model = this.peekAll(modelName).toArray();
model.set('meta', response.meta); model.set('meta', response.meta);
return model; resolve(model);
});
});
}, },
// get cached data // get cached data

View File

@ -20,8 +20,7 @@
} }
} }
.popup-menu-trigger { .popup-menu-trigger {
width: 3rem; min-width: auto;
height: 2rem;
} }
.popup-menu-trigger.is-active { .popup-menu-trigger.is-active {
&, &,
@ -50,6 +49,7 @@
height: auto; height: auto;
width: 100%; width: 100%;
text-align: left; text-align: left;
text-decoration: none;
&:hover { &:hover {
background-color: $menu-item-hover-background-color; 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 { &.is-compact {
height: 2rem; height: 2rem;
padding: $size-11 $size-8; 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 { &.tool-tip-trigger {
color: $black; color: $black;
min-width: auto; min-width: auto;

View File

@ -40,7 +40,7 @@
{{/if}} {{/if}}
</li> </li>
<li class="{{if (is-active-route (array 'vault.cluster.policies' 'vault.cluster.policy')) 'is-active'}}"> <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 Policies
</a> </a>
</li> </li>

View File

@ -10,7 +10,9 @@
{{form-field data-test-field attr=attr model=model}} {{form-field data-test-field attr=attr model=model}}
{{/each}} {{/each}}
</div> </div>
<div class="field is-grouped box is-fullwidth is-bottomless">
<div class="field is-grouped is-grouped-split is-fullwidth box is-bottomless">
<div class="field is-grouped">
<div class="control"> <div class="control">
<button type="submit" data-test-identity-submit=true class="button is-primary {{if save.isRunning 'loading'}}" disabled={{save.isRunning}}> <button type="submit" data-test-identity-submit=true class="button is-primary {{if save.isRunning 'loading'}}" disabled={{save.isRunning}}>
{{#if (eq mode "create")}} {{#if (eq mode "create")}}
@ -20,14 +22,27 @@
{{/if}} {{/if}}
</button> </button>
{{#if (or (eq mode "merge") (eq mode "create" ))}} {{#if (or (eq mode "merge") (eq mode "create" ))}}
<a href={{href-to cancelLink}} class="button"> <a href={{href-to cancelLink}} class="button" data-test-cancel-link>
Cancel Cancel
</a> </a>
{{else}} {{else}}
<a href={{href-to cancelLink model.id "details"}} class="button"> <a href={{href-to cancelLink model.id "details"}} class="button" data-test-cancel-link>
Cancel Cancel
</a> </a>
{{/if}} {{/if}}
</div> </div>
</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> </form>

View File

@ -7,12 +7,12 @@
</div> </div>
<div class="level-right"> <div class="level-right">
{{#if (eq identityType "entity")}} {{#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}} Merge {{pluralize identityType}}
{{i-con glyph="chevron-right" size=11}} {{i-con glyph="chevron-right" size=11}}
</a> </a>
{{/if}} {{/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}} Create {{identityType}}
{{i-con glyph="chevron-right" size=11}} {{i-con glyph="chevron-right" size=11}}
</a> </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="ID" value=model.id }}
{{#info-table-row label=(if (eq model.identityType "entity-alias") "Entity ID" "Group ID") value=model.canonicalId}} {{#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"}} <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}} {{value}}
</div> </div>
<div class="column has-text-right"> <div class="column has-text-right">
{{#if model.canEdit}}
{{identity/popup-metadata params=(array model key)}}
{{/if}}
</div> </div>
</div> </div>
</div> </div>

View File

@ -18,6 +18,7 @@
<code class="has-text-grey is-size-8">{{item.mountAccessor}}</code> <code class="has-text-grey is-size-8">{{item.mountAccessor}}</code>
</div> </div>
<div class="column has-text-right"> <div class="column has-text-right">
{{identity/popup-alias params=(array item)}}
</div> </div>
</div> </div>
{{/linked-block}} {{/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="Type" value=model.type }}
{{info-table-row label="ID" value=model.id }} {{info-table-row label="ID" value=model.id }}
{{#info-table-row label="Merged Ids" value=model.mergedEntityIds }} {{#info-table-row label="Merged Ids" value=model.mergedEntityIds }}

View File

@ -1,21 +1,56 @@
{{#if model.hasMembers}} {{#if model.hasMembers}}
{{#each model.memberGroupIds as |gid|}} {{#each model.memberGroupIds as |gid|}}
<a href={{href-to "vault.cluster.access.identity.show" "groups" gid "details" }}
{{#linked-block
"vault.cluster.access.identity.show"
"groups"
gid
details
class="box is-sideless is-marginless" 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 >{{i-con
glyph='folder' glyph='folder'
size=14 size=14
class="has-text-grey-light" class="has-text-grey-light"
}}{{gid}}</a> }}{{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}}
{{#each model.memberEntityIds as |gid|}} {{#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" class="box is-sideless is-marginless"
}}
<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 >{{i-con
glyph='role' glyph='role'
size=14 size=14
class="has-text-grey-light" class="has-text-grey-light"
}}{{gid}}</a> }}{{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}} {{/each}}
{{else}} {{else}}
<div class="box is-bottomless has-background-white-bis"> <div class="box is-bottomless has-background-white-bis">

View File

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

View File

@ -1,4 +1,4 @@
{{#each model.policies as |item|}} {{#each model.policies as |policyName|}}
{{#linked-block {{#linked-block
"vault.cluster.policy.show" "vault.cluster.policy.show"
"acl" "acl"
@ -7,12 +7,15 @@
}} }}
<div class="columns is-mobile"> <div class="columns is-mobile">
<div class="column is-10"> <div class="column is-10">
<a href={{href-to "vault.cluster.policy.show" "acl" item}} <a href={{href-to "vault.cluster.policy.show" "acl" policyName}}
class="has-text-black has-text-weight-semibold" class="is-block has-text-black has-text-weight-semibold"
><span class="is-underline">{{item}}</span> ><span class="is-underline">{{policyName}}</span>
</a> </a>
</div> </div>
<div class="column has-text-right"> <div class="column has-text-right">
{{#if model.canEdit}}
{{identity/popup-policy params=(array model policyName)}}
{{/if}}
</div> </div>
</div> </div>
{{/linked-block}} {{/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> <ul>
{{yield}}
{{#each secretPath as |path index|}} {{#each secretPath as |path index|}}
<li class="{{if (is-active-route path.path path.model isExact=true) 'is-active'}}"> <li class="{{if (is-active-route path.path path.model isExact=true) 'is-active'}}">
<span class="sep">&#x0002f;</span> <span class="sep">&#x0002f;</span>

View File

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