diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3bfef15624..1b2012eb45 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,16 @@ IMPROVEMENTS:
* auth/ldap: Use the value of the `LOGNAME` or `USER` env vars for the
username if not explicitly set on the command line when authenticating
[GH-2154]
+ * audit: Support adding a configurable prefix (such as `@cee`) before each
+ line [GH-2359]
+
+BUG FIXES:
+
+ * auth/aws-ec2: Return role period in seconds and not nanoseconds [GH-2374]
+ * auth/okta: Fix panic if user had no local groups and/or policies set
+ [GH-2367]
+ * command/server: Fix parsing of redirect address when port is not mentioned
+ [GH-2354]
## 0.6.5 (February 7th, 2017)
diff --git a/audit/format_json.go b/audit/format_json.go
index 241be345c0..9e200f0032 100644
--- a/audit/format_json.go
+++ b/audit/format_json.go
@@ -8,13 +8,22 @@ import (
// JSONFormatWriter is an AuditFormatWriter implementation that structures data into
// a JSON format.
-type JSONFormatWriter struct{}
+type JSONFormatWriter struct {
+ Prefix string
+}
func (f *JSONFormatWriter) WriteRequest(w io.Writer, req *AuditRequestEntry) error {
if req == nil {
return fmt.Errorf("request entry was nil, cannot encode")
}
+ if len(f.Prefix) > 0 {
+ _, err := w.Write([]byte(f.Prefix))
+ if err != nil {
+ return err
+ }
+ }
+
enc := json.NewEncoder(w)
return enc.Encode(req)
}
@@ -24,6 +33,13 @@ func (f *JSONFormatWriter) WriteResponse(w io.Writer, resp *AuditResponseEntry)
return fmt.Errorf("response entry was nil, cannot encode")
}
+ if len(f.Prefix) > 0 {
+ _, err := w.Write([]byte(f.Prefix))
+ if err != nil {
+ return err
+ }
+ }
+
enc := json.NewEncoder(w)
return enc.Encode(resp)
}
diff --git a/audit/format_json_test.go b/audit/format_json_test.go
index 5a70f7d704..21bb647856 100644
--- a/audit/format_json_test.go
+++ b/audit/format_json_test.go
@@ -19,6 +19,7 @@ func TestFormatJSON_formatRequest(t *testing.T) {
Auth *logical.Auth
Req *logical.Request
Err error
+ Prefix string
Result string
}{
"auth, request": {
@@ -37,6 +38,26 @@ func TestFormatJSON_formatRequest(t *testing.T) {
},
},
errors.New("this is an error"),
+ "",
+ testFormatJSONReqBasicStr,
+ },
+ "auth, request with prefix": {
+ &logical.Auth{ClientToken: "foo", Policies: []string{"root"}},
+ &logical.Request{
+ Operation: logical.UpdateOperation,
+ Path: "/foo",
+ Connection: &logical.Connection{
+ RemoteAddr: "127.0.0.1",
+ },
+ WrapInfo: &logical.RequestWrapInfo{
+ TTL: 60 * time.Second,
+ },
+ Headers: map[string][]string{
+ "foo": []string{"bar"},
+ },
+ },
+ errors.New("this is an error"),
+ "@cee: ",
testFormatJSONReqBasicStr,
},
}
@@ -44,7 +65,9 @@ func TestFormatJSON_formatRequest(t *testing.T) {
for name, tc := range cases {
var buf bytes.Buffer
formatter := AuditFormatter{
- AuditFormatWriter: &JSONFormatWriter{},
+ AuditFormatWriter: &JSONFormatWriter{
+ Prefix: tc.Prefix,
+ },
}
salter, _ := salt.NewSalt(nil, nil)
config := FormatterConfig{
@@ -54,13 +77,17 @@ func TestFormatJSON_formatRequest(t *testing.T) {
t.Fatalf("bad: %s\nerr: %s", name, err)
}
+ if !strings.HasPrefix(buf.String(), tc.Prefix) {
+ t.Fatalf("no prefix: %s \n log: %s\nprefix: %s", name, tc.Result, tc.Prefix)
+ }
+
var expectedjson = new(AuditRequestEntry)
if err := jsonutil.DecodeJSON([]byte(tc.Result), &expectedjson); err != nil {
t.Fatalf("bad json: %s", err)
}
var actualjson = new(AuditRequestEntry)
- if err := jsonutil.DecodeJSON([]byte(buf.String()), &actualjson); err != nil {
+ if err := jsonutil.DecodeJSON([]byte(buf.String())[len(tc.Prefix):], &actualjson); err != nil {
t.Fatalf("bad json: %s", err)
}
@@ -71,7 +98,7 @@ func TestFormatJSON_formatRequest(t *testing.T) {
t.Fatalf("unable to marshal json: %s", err)
}
- if strings.TrimSpace(buf.String()) != string(expectedBytes) {
+ if !strings.HasSuffix(strings.TrimSpace(buf.String()), string(expectedBytes)) {
t.Fatalf(
"bad: %s\nResult:\n\n'%s'\n\nExpected:\n\n'%s'",
name, buf.String(), string(expectedBytes))
diff --git a/audit/format_jsonx.go b/audit/format_jsonx.go
index b3c814f72f..cc6cc956be 100644
--- a/audit/format_jsonx.go
+++ b/audit/format_jsonx.go
@@ -10,13 +10,22 @@ import (
// JSONxFormatWriter is an AuditFormatWriter implementation that structures data into
// a XML format.
-type JSONxFormatWriter struct{}
+type JSONxFormatWriter struct {
+ Prefix string
+}
func (f *JSONxFormatWriter) WriteRequest(w io.Writer, req *AuditRequestEntry) error {
if req == nil {
return fmt.Errorf("request entry was nil, cannot encode")
}
+ if len(f.Prefix) > 0 {
+ _, err := w.Write([]byte(f.Prefix))
+ if err != nil {
+ return err
+ }
+ }
+
jsonBytes, err := json.Marshal(req)
if err != nil {
return err
@@ -36,6 +45,13 @@ func (f *JSONxFormatWriter) WriteResponse(w io.Writer, resp *AuditResponseEntry)
return fmt.Errorf("response entry was nil, cannot encode")
}
+ if len(f.Prefix) > 0 {
+ _, err := w.Write([]byte(f.Prefix))
+ if err != nil {
+ return err
+ }
+ }
+
jsonBytes, err := json.Marshal(resp)
if err != nil {
return err
diff --git a/audit/format_jsonx_test.go b/audit/format_jsonx_test.go
index be63ae39d7..8d4fe4ba29 100644
--- a/audit/format_jsonx_test.go
+++ b/audit/format_jsonx_test.go
@@ -17,6 +17,7 @@ func TestFormatJSONx_formatRequest(t *testing.T) {
Auth *logical.Auth
Req *logical.Request
Err error
+ Prefix string
Result string
Expected string
}{
@@ -37,6 +38,27 @@ func TestFormatJSONx_formatRequest(t *testing.T) {
},
errors.New("this is an error"),
"",
+ "",
+ `rootthis is an errorbarupdate/foo127.0.0.160request`,
+ },
+ "auth, request with prefix": {
+ &logical.Auth{ClientToken: "foo", Policies: []string{"root"}},
+ &logical.Request{
+ Operation: logical.UpdateOperation,
+ Path: "/foo",
+ Connection: &logical.Connection{
+ RemoteAddr: "127.0.0.1",
+ },
+ WrapInfo: &logical.RequestWrapInfo{
+ TTL: 60 * time.Second,
+ },
+ Headers: map[string][]string{
+ "foo": []string{"bar"},
+ },
+ },
+ errors.New("this is an error"),
+ "",
+ "@cee: ",
`rootthis is an errorbarupdate/foo127.0.0.160request`,
},
}
@@ -44,7 +66,9 @@ func TestFormatJSONx_formatRequest(t *testing.T) {
for name, tc := range cases {
var buf bytes.Buffer
formatter := AuditFormatter{
- AuditFormatWriter: &JSONxFormatWriter{},
+ AuditFormatWriter: &JSONxFormatWriter{
+ Prefix: tc.Prefix,
+ },
}
salter, _ := salt.NewSalt(nil, nil)
config := FormatterConfig{
@@ -55,7 +79,11 @@ func TestFormatJSONx_formatRequest(t *testing.T) {
t.Fatalf("bad: %s\nerr: %s", name, err)
}
- if strings.TrimSpace(buf.String()) != string(tc.Expected) {
+ if !strings.HasPrefix(buf.String(), tc.Prefix) {
+ t.Fatalf("no prefix: %s \n log: %s\nprefix: %s", name, tc.Result, tc.Prefix)
+ }
+
+ if !strings.HasSuffix(strings.TrimSpace(buf.String()), string(tc.Expected)) {
t.Fatalf(
"bad: %s\nResult:\n\n'%s'\n\nExpected:\n\n'%s'",
name, strings.TrimSpace(buf.String()), string(tc.Expected))
diff --git a/builtin/audit/file/backend.go b/builtin/audit/file/backend.go
index 0318052a4b..359d9242ef 100644
--- a/builtin/audit/file/backend.go
+++ b/builtin/audit/file/backend.go
@@ -76,9 +76,13 @@ func Factory(conf *audit.BackendConfig) (audit.Backend, error) {
switch format {
case "json":
- b.formatter.AuditFormatWriter = &audit.JSONFormatWriter{}
+ b.formatter.AuditFormatWriter = &audit.JSONFormatWriter{
+ Prefix: conf.Config["prefix"],
+ }
case "jsonx":
- b.formatter.AuditFormatWriter = &audit.JSONxFormatWriter{}
+ b.formatter.AuditFormatWriter = &audit.JSONxFormatWriter{
+ Prefix: conf.Config["prefix"],
+ }
}
// Ensure that the file can be successfully opened for writing;
diff --git a/builtin/audit/socket/backend.go b/builtin/audit/socket/backend.go
index 24d600e7be..e987d33a94 100644
--- a/builtin/audit/socket/backend.go
+++ b/builtin/audit/socket/backend.go
@@ -87,9 +87,13 @@ func Factory(conf *audit.BackendConfig) (audit.Backend, error) {
switch format {
case "json":
- b.formatter.AuditFormatWriter = &audit.JSONFormatWriter{}
+ b.formatter.AuditFormatWriter = &audit.JSONFormatWriter{
+ Prefix: conf.Config["prefix"],
+ }
case "jsonx":
- b.formatter.AuditFormatWriter = &audit.JSONxFormatWriter{}
+ b.formatter.AuditFormatWriter = &audit.JSONxFormatWriter{
+ Prefix: conf.Config["prefix"],
+ }
}
return b, nil
diff --git a/builtin/audit/syslog/backend.go b/builtin/audit/syslog/backend.go
index 3056a64c91..4b1912f67e 100644
--- a/builtin/audit/syslog/backend.go
+++ b/builtin/audit/syslog/backend.go
@@ -74,9 +74,13 @@ func Factory(conf *audit.BackendConfig) (audit.Backend, error) {
switch format {
case "json":
- b.formatter.AuditFormatWriter = &audit.JSONFormatWriter{}
+ b.formatter.AuditFormatWriter = &audit.JSONFormatWriter{
+ Prefix: conf.Config["prefix"],
+ }
case "jsonx":
- b.formatter.AuditFormatWriter = &audit.JSONxFormatWriter{}
+ b.formatter.AuditFormatWriter = &audit.JSONxFormatWriter{
+ Prefix: conf.Config["prefix"],
+ }
}
return b, nil
diff --git a/builtin/credential/aws-ec2/path_role.go b/builtin/credential/aws-ec2/path_role.go
index e35cf089aa..f57b5f61aa 100644
--- a/builtin/credential/aws-ec2/path_role.go
+++ b/builtin/credential/aws-ec2/path_role.go
@@ -272,10 +272,10 @@ func (b *backend) pathRoleRead(
// HMAC key belonging to the role should NOT be exported.
delete(respData, "hmac_key")
- // Display the ttl in seconds.
+ // Display all the durations in seconds
respData["ttl"] = roleEntry.TTL / time.Second
- // Display the max_ttl in seconds.
respData["max_ttl"] = roleEntry.MaxTTL / time.Second
+ respData["period"] = roleEntry.Period / time.Second
return &logical.Response{
Data: respData,
diff --git a/builtin/credential/aws-ec2/path_role_test.go b/builtin/credential/aws-ec2/path_role_test.go
new file mode 100644
index 0000000000..983f6762b1
--- /dev/null
+++ b/builtin/credential/aws-ec2/path_role_test.go
@@ -0,0 +1,59 @@
+package awsec2
+
+import (
+ "testing"
+ "time"
+
+ "github.com/hashicorp/vault/logical"
+)
+
+func TestAwsEc2_RoleDurationSeconds(t *testing.T) {
+ config := logical.TestBackendConfig()
+ storage := &logical.InmemStorage{}
+ config.StorageView = storage
+
+ b, err := Backend(config)
+ if err != nil {
+ t.Fatal(err)
+ }
+ _, err = b.Setup(config)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ roleData := map[string]interface{}{
+ "bound_iam_instance_profile_arn": "testarn",
+ "ttl": "10s",
+ "max_ttl": "20s",
+ "period": "30s",
+ }
+
+ roleReq := &logical.Request{
+ Operation: logical.UpdateOperation,
+ Storage: storage,
+ Path: "role/testrole",
+ Data: roleData,
+ }
+
+ resp, err := b.HandleRequest(roleReq)
+ if err != nil || (resp != nil && resp.IsError()) {
+ t.Fatalf("resp: %#v, err: %v", resp, err)
+ }
+
+ roleReq.Operation = logical.ReadOperation
+
+ resp, err = b.HandleRequest(roleReq)
+ if err != nil || (resp != nil && resp.IsError()) {
+ t.Fatalf("resp: %#v, err: %v", resp, err)
+ }
+
+ if int64(resp.Data["ttl"].(time.Duration)) != 10 {
+ t.Fatalf("bad: period; expected: 10, actual: %d", resp.Data["ttl"])
+ }
+ if int64(resp.Data["max_ttl"].(time.Duration)) != 20 {
+ t.Fatalf("bad: period; expected: 20, actual: %d", resp.Data["max_ttl"])
+ }
+ if int64(resp.Data["period"].(time.Duration)) != 30 {
+ t.Fatalf("bad: period; expected: 30, actual: %d", resp.Data["period"])
+ }
+}
diff --git a/builtin/credential/ldap/backend_test.go b/builtin/credential/ldap/backend_test.go
index 5c7628ff92..51b4df790b 100644
--- a/builtin/credential/ldap/backend_test.go
+++ b/builtin/credential/ldap/backend_test.go
@@ -135,8 +135,7 @@ func TestBackend_basic(t *testing.T) {
b := factory(t)
logicaltest.Test(t, logicaltest.TestCase{
- AcceptanceTest: true,
- Backend: b,
+ Backend: b,
Steps: []logicaltest.TestStep{
testAccStepConfigUrl(t),
// Map Scientists group (from LDAP server) with foo policy
@@ -164,8 +163,7 @@ func TestBackend_basic_authbind(t *testing.T) {
b := factory(t)
logicaltest.Test(t, logicaltest.TestCase{
- AcceptanceTest: true,
- Backend: b,
+ Backend: b,
Steps: []logicaltest.TestStep{
testAccStepConfigUrlWithAuthBind(t),
testAccStepGroup(t, "Scientists", "foo"),
@@ -180,8 +178,7 @@ func TestBackend_basic_discover(t *testing.T) {
b := factory(t)
logicaltest.Test(t, logicaltest.TestCase{
- AcceptanceTest: true,
- Backend: b,
+ Backend: b,
Steps: []logicaltest.TestStep{
testAccStepConfigUrlWithDiscover(t),
testAccStepGroup(t, "Scientists", "foo"),
@@ -196,8 +193,7 @@ func TestBackend_basic_nogroupdn(t *testing.T) {
b := factory(t)
logicaltest.Test(t, logicaltest.TestCase{
- AcceptanceTest: true,
- Backend: b,
+ Backend: b,
Steps: []logicaltest.TestStep{
testAccStepConfigUrlNoGroupDN(t),
testAccStepGroup(t, "Scientists", "foo"),
@@ -212,8 +208,7 @@ func TestBackend_groupCrud(t *testing.T) {
b := factory(t)
logicaltest.Test(t, logicaltest.TestCase{
- AcceptanceTest: true,
- Backend: b,
+ Backend: b,
Steps: []logicaltest.TestStep{
testAccStepGroup(t, "g1", "foo"),
testAccStepReadGroup(t, "g1", "default,foo"),
@@ -230,8 +225,7 @@ func TestBackend_configDefaultsAfterUpdate(t *testing.T) {
b := factory(t)
logicaltest.Test(t, logicaltest.TestCase{
- AcceptanceTest: false,
- Backend: b,
+ Backend: b,
Steps: []logicaltest.TestStep{
logicaltest.TestStep{
Operation: logical.UpdateOperation,
@@ -297,7 +291,8 @@ func testAccStepConfigUrlWithAuthBind(t *testing.T) logicaltest.TestStep {
Data: map[string]interface{}{
// Online LDAP test server
// http://www.forumsys.com/tutorials/integration-how-to/ldap/online-ldap-test-server/
- "url": "ldap://ldap.forumsys.com",
+ // In this test we also exercise multiple URL support
+ "url": "foobar://ldap.example.com,ldap://ldap.forumsys.com",
"userattr": "uid",
"userdn": "dc=example,dc=com",
"groupdn": "dc=example,dc=com",
@@ -388,8 +383,7 @@ func TestBackend_userCrud(t *testing.T) {
b := Backend()
logicaltest.Test(t, logicaltest.TestCase{
- AcceptanceTest: true,
- Backend: b,
+ Backend: b,
Steps: []logicaltest.TestStep{
testAccStepUser(t, "g1", "bar"),
testAccStepReadUser(t, "g1", "bar"),
diff --git a/builtin/credential/ldap/path_config.go b/builtin/credential/ldap/path_config.go
index 1a2f50555d..b32aa10875 100644
--- a/builtin/credential/ldap/path_config.go
+++ b/builtin/credential/ldap/path_config.go
@@ -11,9 +11,11 @@ import (
"github.com/fatih/structs"
"github.com/go-ldap/ldap"
+ multierror "github.com/hashicorp/go-multierror"
"github.com/hashicorp/vault/helper/tlsutil"
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/logical/framework"
+ log "github.com/mgutz/logxi/v1"
)
func pathConfig(b *backend) *framework.Path {
@@ -23,7 +25,7 @@ func pathConfig(b *backend) *framework.Path {
"url": &framework.FieldSchema{
Type: framework.TypeString,
Default: "ldap://127.0.0.1",
- Description: "ldap URL to connect to (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{
@@ -155,6 +157,8 @@ func (b *backend) Config(req *logical.Request) (*ConfigEntry, error) {
return nil, err
}
+ result.logger = b.Logger()
+
return result, nil
}
@@ -183,6 +187,8 @@ func (b *backend) pathConfigRead(
func (b *backend) newConfigEntry(d *framework.FieldData) (*ConfigEntry, error) {
cfg := new(ConfigEntry)
+ cfg.logger = b.Logger()
+
url := d.Get("url").(string)
if url != "" {
cfg.Url = strings.ToLower(url)
@@ -294,6 +300,7 @@ func (b *backend) pathConfigWrite(
}
type ConfigEntry struct {
+ logger log.Logger
Url string `json:"url" structs:"url" mapstructure:"url"`
UserDN string `json:"userdn" structs:"userdn" mapstructure:"userdn"`
GroupDN string `json:"groupdn" structs:"groupdn" mapstructure:"groupdn"`
@@ -348,55 +355,67 @@ func (c *ConfigEntry) GetTLSConfig(host string) (*tls.Config, error) {
}
func (c *ConfigEntry) DialLDAP() (*ldap.Conn, error) {
-
- u, err := url.Parse(c.Url)
- if err != nil {
- return nil, err
- }
- host, port, err := net.SplitHostPort(u.Host)
- if err != nil {
- host = u.Host
- }
-
+ var retErr *multierror.Error
var conn *ldap.Conn
- var tlsConfig *tls.Config
- switch u.Scheme {
- case "ldap":
- if port == "" {
- port = "389"
- }
- conn, err = ldap.Dial("tcp", host+":"+port)
+ urls := strings.Split(c.Url, ",")
+ for _, uut := range urls {
+ u, err := url.Parse(uut)
if err != nil {
- break
+ retErr = multierror.Append(retErr, fmt.Errorf("error parsing url %q: %s", uut, err.Error()))
+ continue
}
- if conn == nil {
- err = fmt.Errorf("empty connection after dialing")
- break
+ host, port, err := net.SplitHostPort(u.Host)
+ if err != nil {
+ host = u.Host
}
- if c.StartTLS {
+
+ var tlsConfig *tls.Config
+ switch u.Scheme {
+ case "ldap":
+ if port == "" {
+ port = "389"
+ }
+ conn, err = ldap.Dial("tcp", net.JoinHostPort(host, port))
+ if err != nil {
+ break
+ }
+ if conn == nil {
+ err = fmt.Errorf("empty connection after dialing")
+ break
+ }
+ if c.StartTLS {
+ tlsConfig, err = c.GetTLSConfig(host)
+ if err != nil {
+ break
+ }
+ err = conn.StartTLS(tlsConfig)
+ }
+ case "ldaps":
+ if port == "" {
+ port = "636"
+ }
tlsConfig, err = c.GetTLSConfig(host)
if err != nil {
break
}
- err = conn.StartTLS(tlsConfig)
+ conn, err = ldap.DialTLS("tcp", net.JoinHostPort(host, port), tlsConfig)
+ default:
+ retErr = multierror.Append(retErr, fmt.Errorf("invalid LDAP scheme in url %q"))
+ continue
}
- case "ldaps":
- if port == "" {
- port = "636"
- }
- tlsConfig, err = c.GetTLSConfig(host)
- if err != nil {
+ if err == nil {
+ if retErr != nil {
+ if c.logger.IsDebug() {
+ c.logger.Debug("ldap: errors connecting to some hosts: %s", retErr.Error())
+ }
+ }
+ retErr = nil
break
}
- conn, err = ldap.DialTLS("tcp", host+":"+port, tlsConfig)
- default:
- return nil, fmt.Errorf("invalid LDAP scheme")
- }
- if err != nil {
- return nil, fmt.Errorf("cannot connect to LDAP: %v", err)
+ retErr = multierror.Append(retErr, fmt.Errorf("error connecting to host %q: %s", uut, err.Error()))
}
- return conn, nil
+ return conn, retErr.ErrorOrNil()
}
/*
diff --git a/builtin/credential/okta/backend.go b/builtin/credential/okta/backend.go
index 6e4f169271..43a16473a9 100644
--- a/builtin/credential/okta/backend.go
+++ b/builtin/credential/okta/backend.go
@@ -59,10 +59,6 @@ func (b *backend) Login(req *logical.Request, username string, password string)
return nil, logical.ErrorResponse("okta auth backend unexpected failure"), nil
}
- if b.Logger().IsDebug() {
- b.Logger().Debug("auth/okta:", auth)
- }
-
oktaGroups, err := b.getOktaGroups(cfg, auth.Embedded.User.ID)
if err != nil {
return nil, logical.ErrorResponse(err.Error()), nil
@@ -96,13 +92,15 @@ func (b *backend) Login(req *logical.Request, username string, password string)
var policies []string
for _, groupName := range allGroups {
group, err := b.Group(req.Storage, groupName)
- if err == nil && group != nil {
+ if err == nil && group != nil && group.Policies != nil {
policies = append(policies, group.Policies...)
}
}
// Merge local Policies into Okta Policies
- policies = append(policies, user.Policies...)
+ if user != nil && user.Policies != nil {
+ policies = append(policies, user.Policies...)
+ }
if len(policies) == 0 {
errStr := "user is not a member of any authorized policy"
diff --git a/builtin/credential/okta/backend_test.go b/builtin/credential/okta/backend_test.go
index b808caf8a9..7672dc0998 100644
--- a/builtin/credential/okta/backend_test.go
+++ b/builtin/credential/okta/backend_test.go
@@ -7,6 +7,7 @@ import (
"testing"
"github.com/hashicorp/vault/helper/logformat"
+ "github.com/hashicorp/vault/helper/policyutil"
log "github.com/mgutz/logxi/v1"
"github.com/hashicorp/vault/logical"
@@ -40,23 +41,23 @@ func TestBackend_Config(t *testing.T) {
Backend: b,
Steps: []logicaltest.TestStep{
testConfigCreate(t, configData),
- testLoginWrite(t, username, "wrong", "E0000004", 0),
- testLoginWrite(t, username, password, "user is not a member of any authorized policy", 0),
+ testLoginWrite(t, username, "wrong", "E0000004", nil),
+ testLoginWrite(t, username, password, "user is not a member of any authorized policy", nil),
testAccUserGroups(t, username, "local_group,local_group2"),
testAccGroups(t, "local_group", "local_group_policy"),
- testLoginWrite(t, username, password, "", 2),
+ testLoginWrite(t, username, password, "", []string{"local_group_policy"}),
testAccGroups(t, "Everyone", "everyone_group_policy,every_group_policy2"),
- testLoginWrite(t, username, password, "", 2),
+ testLoginWrite(t, username, password, "", []string{"local_group_policy"}),
testConfigUpdate(t, configDataToken),
testConfigRead(t, configData),
- testLoginWrite(t, username, password, "", 4),
- testAccGroups(t, "TestGroup", "testgroup_group_policy"),
- testLoginWrite(t, username, password, "", 5),
+ testLoginWrite(t, username, password, "", []string{"everyone_group_policy", "every_group_policy2", "local_group_policy"}),
+ testAccGroups(t, "local_group2", "testgroup_group_policy"),
+ testLoginWrite(t, username, password, "", []string{"everyone_group_policy", "every_group_policy2", "local_group_policy", "testgroup_group_policy"}),
},
})
}
-func testLoginWrite(t *testing.T, username, password, reason string, policies int) logicaltest.TestStep {
+func testLoginWrite(t *testing.T, username, password, reason string, policies []string) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.UpdateOperation,
Path: "login/" + username,
@@ -72,8 +73,8 @@ func testLoginWrite(t *testing.T, username, password, reason string, policies in
}
if resp.Auth != nil {
- if len(resp.Auth.Policies) != policies {
- return fmt.Errorf("policy mismatch expected %d but got %s", policies, resp.Auth.Policies)
+ if !policyutil.EquivalentPolicies(resp.Auth.Policies, policies) {
+ return fmt.Errorf("policy mismatch expected %v but got %v", policies, resp.Auth.Policies)
}
}
diff --git a/command/server.go b/command/server.go
index f2cd7413e3..cf8a442d49 100644
--- a/command/server.go
+++ b/command/server.go
@@ -312,15 +312,19 @@ func (c *ServerCommand) Run(args []string) int {
return 1
}
host, port, err := net.SplitHostPort(u.Host)
- nPort, nPortErr := strconv.Atoi(port)
if err != nil {
- // assume it's due to there not being a port specified, in which case
- // use 443
- host = u.Host
- nPort = 443
+ // This sucks, as it's a const in the function but not exported in the package
+ if strings.Contains(err.Error(), "missing port in address") {
+ host = u.Host
+ port = "443"
+ } else {
+ c.Ui.Output(fmt.Sprintf("Error parsing redirect address: %v", err))
+ return 1
+ }
}
- if nPortErr != nil {
- c.Ui.Output(fmt.Sprintf("Cannot parse %s as a numeric port: %v", port, nPortErr))
+ nPort, err := strconv.Atoi(port)
+ if err != nil {
+ c.Ui.Output(fmt.Sprintf("Error parsing redirect address; failed to convert %q to a numeric: %v", port, err))
return 1
}
u.Host = net.JoinHostPort(host, strconv.Itoa(nPort+1))
diff --git a/vault/audited_headers.go b/vault/audited_headers.go
index 18b4aea3ca..e7cb69a63c 100644
--- a/vault/audited_headers.go
+++ b/vault/audited_headers.go
@@ -2,11 +2,15 @@ package vault
import (
"fmt"
+ "strings"
"sync"
"github.com/hashicorp/vault/logical"
)
+// N.B.: While we could use textproto to get the canonical mime header, HTTP/2
+// requires all headers to be converted to lower case, so we just do that.
+
const (
// Key used in the BarrierView to store and retrieve the header config
auditedHeadersEntry = "audited-headers"
@@ -37,7 +41,7 @@ func (a *AuditedHeadersConfig) add(header string, hmac bool) error {
a.Lock()
defer a.Unlock()
- a.Headers[header] = &auditedHeaderSettings{hmac}
+ a.Headers[strings.ToLower(header)] = &auditedHeaderSettings{hmac}
entry, err := logical.StorageEntryJSON(auditedHeadersEntry, a.Headers)
if err != nil {
return fmt.Errorf("failed to persist audited headers config: %v", err)
@@ -60,7 +64,7 @@ func (a *AuditedHeadersConfig) remove(header string) error {
a.Lock()
defer a.Unlock()
- delete(a.Headers, header)
+ delete(a.Headers, strings.ToLower(header))
entry, err := logical.StorageEntryJSON(auditedHeadersEntry, a.Headers)
if err != nil {
return fmt.Errorf("failed to persist audited headers config: %v", err)
@@ -80,9 +84,16 @@ func (a *AuditedHeadersConfig) ApplyConfig(headers map[string][]string, hashFunc
a.RLock()
defer a.RUnlock()
+ // Make a copy of the incoming headers with everything lower so we can
+ // case-insensitively compare
+ lowerHeaders := make(map[string][]string, len(headers))
+ for k, v := range headers {
+ lowerHeaders[strings.ToLower(k)] = v
+ }
+
result = make(map[string][]string, len(a.Headers))
for key, settings := range a.Headers {
- if val, ok := headers[key]; ok {
+ if val, ok := lowerHeaders[key]; ok {
// copy the header values so we don't overwrite them
hVals := make([]string, len(val))
copy(hVals, val)
@@ -120,8 +131,15 @@ func (c *Core) setupAuditedHeadersConfig() error {
}
}
+ // Ensure that we are able to case-sensitively access the headers;
+ // necessary for the upgrade case
+ lowerHeaders := make(map[string]*auditedHeaderSettings, len(headers))
+ for k, v := range headers {
+ lowerHeaders[strings.ToLower(k)] = v
+ }
+
c.auditedHeaders = &AuditedHeadersConfig{
- Headers: headers,
+ Headers: lowerHeaders,
view: view,
}
diff --git a/vault/audited_headers_test.go b/vault/audited_headers_test.go
index 07da7c9c53..5e82ec71dc 100644
--- a/vault/audited_headers_test.go
+++ b/vault/audited_headers_test.go
@@ -29,7 +29,7 @@ func testAuditedHeadersConfig_Add(t *testing.T, conf *AuditedHeadersConfig) {
t.Fatalf("Error when adding header to config: %s", err)
}
- settings, ok := conf.Headers["X-Test-Header"]
+ settings, ok := conf.Headers["x-test-header"]
if !ok {
t.Fatal("Expected header to be found in config")
}
@@ -50,7 +50,7 @@ func testAuditedHeadersConfig_Add(t *testing.T, conf *AuditedHeadersConfig) {
}
expected := map[string]*auditedHeaderSettings{
- "X-Test-Header": &auditedHeaderSettings{
+ "x-test-header": &auditedHeaderSettings{
HMAC: false,
},
}
@@ -64,7 +64,7 @@ func testAuditedHeadersConfig_Add(t *testing.T, conf *AuditedHeadersConfig) {
t.Fatalf("Error when adding header to config: %s", err)
}
- settings, ok = conf.Headers["X-Vault-Header"]
+ settings, ok = conf.Headers["x-vault-header"]
if !ok {
t.Fatal("Expected header to be found in config")
}
@@ -84,7 +84,7 @@ func testAuditedHeadersConfig_Add(t *testing.T, conf *AuditedHeadersConfig) {
t.Fatalf("Error decoding header view: %s", err)
}
- expected["X-Vault-Header"] = &auditedHeaderSettings{
+ expected["x-vault-header"] = &auditedHeaderSettings{
HMAC: true,
}
@@ -100,7 +100,7 @@ func testAuditedHeadersConfig_Remove(t *testing.T, conf *AuditedHeadersConfig) {
t.Fatalf("Error when adding header to config: %s", err)
}
- _, ok := conf.Headers["X-Test-Header"]
+ _, ok := conf.Headers["x-Test-HeAder"]
if ok {
t.Fatal("Expected header to not be found in config")
}
@@ -117,7 +117,7 @@ func testAuditedHeadersConfig_Remove(t *testing.T, conf *AuditedHeadersConfig) {
}
expected := map[string]*auditedHeaderSettings{
- "X-Vault-Header": &auditedHeaderSettings{
+ "x-vault-header": &auditedHeaderSettings{
HMAC: true,
},
}
@@ -126,12 +126,12 @@ func testAuditedHeadersConfig_Remove(t *testing.T, conf *AuditedHeadersConfig) {
t.Fatalf("Expected config didn't match actual. Expected: %#v, Got: %#v", expected, headers)
}
- err = conf.remove("X-Vault-Header")
+ err = conf.remove("x-VaulT-Header")
if err != nil {
t.Fatalf("Error when adding header to config: %s", err)
}
- _, ok = conf.Headers["X-Vault-Header"]
+ _, ok = conf.Headers["x-vault-header"]
if ok {
t.Fatal("Expected header to not be found in config")
}
@@ -157,10 +157,8 @@ func testAuditedHeadersConfig_Remove(t *testing.T, conf *AuditedHeadersConfig) {
func TestAuditedHeadersConfig_ApplyConfig(t *testing.T) {
conf := mockAuditedHeadersConfig(t)
- conf.Headers = map[string]*auditedHeaderSettings{
- "X-Test-Header": &auditedHeaderSettings{false},
- "X-Vault-Header": &auditedHeaderSettings{true},
- }
+ conf.add("X-TesT-Header", false)
+ conf.add("X-Vault-HeAdEr", true)
reqHeaders := map[string][]string{
"X-Test-Header": []string{"foo"},
@@ -173,8 +171,8 @@ func TestAuditedHeadersConfig_ApplyConfig(t *testing.T) {
result := conf.ApplyConfig(reqHeaders, hashFunc)
expected := map[string][]string{
- "X-Test-Header": []string{"foo"},
- "X-Vault-Header": []string{"hashed", "hashed"},
+ "x-test-header": []string{"foo"},
+ "x-vault-header": []string{"hashed", "hashed"},
}
if !reflect.DeepEqual(result, expected) {
diff --git a/vendor/github.com/hashicorp/go-cleanhttp/cleanhttp.go b/vendor/github.com/hashicorp/go-cleanhttp/cleanhttp.go
index f4596d80cd..74be85e9d7 100644
--- a/vendor/github.com/hashicorp/go-cleanhttp/cleanhttp.go
+++ b/vendor/github.com/hashicorp/go-cleanhttp/cleanhttp.go
@@ -3,6 +3,7 @@ package cleanhttp
import (
"net"
"net/http"
+ "runtime"
"time"
)
@@ -22,13 +23,15 @@ func DefaultTransport() *http.Transport {
func DefaultPooledTransport() *http.Transport {
transport := &http.Transport{
Proxy: http.ProxyFromEnvironment,
- Dial: (&net.Dialer{
+ DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
- }).Dial,
- TLSHandshakeTimeout: 10 * time.Second,
- DisableKeepAlives: false,
- MaxIdleConnsPerHost: 1,
+ }).DialContext,
+ MaxIdleConns: 100,
+ IdleConnTimeout: 90 * time.Second,
+ TLSHandshakeTimeout: 10 * time.Second,
+ ExpectContinueTimeout: 1 * time.Second,
+ MaxIdleConnsPerHost: runtime.GOMAXPROCS(0) + 1,
}
return transport
}
diff --git a/vendor/vendor.json b/vendor/vendor.json
index 2e14dafe45..ed61078f52 100644
--- a/vendor/vendor.json
+++ b/vendor/vendor.json
@@ -601,10 +601,10 @@
"revisionTime": "2014-10-28T05:47:10Z"
},
{
- "checksumSHA1": "Uzyon2091lmwacNsl1hCytjhHtg=",
+ "checksumSHA1": "6ihdHMkDfFx/rJ1A36com2F6bQk=",
"path": "github.com/hashicorp/go-cleanhttp",
- "revision": "ad28ea4487f05916463e2423a55166280e8254b5",
- "revisionTime": "2016-04-07T17:41:26Z"
+ "revision": "a45970658e51fea2c41445ff0f7e07106d007617",
+ "revisionTime": "2017-02-11T00:33:01Z"
},
{
"checksumSHA1": "TNlVzNR1OaajcNi3CbQ3bGbaLGU=",
diff --git a/website/scripts/deploy.sh b/website/scripts/deploy.sh
index 297c12fe4f..16a8653bae 100755
--- a/website/scripts/deploy.sh
+++ b/website/scripts/deploy.sh
@@ -58,7 +58,7 @@ if [ -z "$NO_UPLOAD" ]; then
--no-mime-magic \
--acl-public \
--recursive \
- --add-header="Cache-Control: max-age=31536000" \
+ --add-header="Cache-Control: max-age=14400" \
--add-header="x-amz-meta-surrogate-key: site-$PROJECT" \
sync "$DIR/build/" "s3://hc-sites/$PROJECT/latest/"
@@ -67,6 +67,7 @@ if [ -z "$NO_UPLOAD" ]; then
echo "Overriding javascript mime-types..."
s3cmd \
--mime-type="application/javascript" \
+ --add-header="Cache-Control: max-age=31536000" \
--exclude "*" \
--include "*.js" \
--recursive \
@@ -75,6 +76,7 @@ if [ -z "$NO_UPLOAD" ]; then
echo "Overriding css mime-types..."
s3cmd \
--mime-type="text/css" \
+ --add-header="Cache-Control: max-age=31536000" \
--exclude "*" \
--include "*.css" \
--recursive \
@@ -83,11 +85,11 @@ if [ -z "$NO_UPLOAD" ]; then
echo "Overriding svg mime-types..."
s3cmd \
--mime-type="image/svg+xml" \
+ --add-header="Cache-Control: max-age=31536000" \
--exclude "*" \
--include "*.svg" \
--recursive \
modify "s3://hc-sites/$PROJECT/latest/"
-
fi
# Perform a soft-purge of the surrogate key.
diff --git a/website/source/docs/audit/file.html.md b/website/source/docs/audit/file.html.md
index 5d942930c7..087b37758f 100644
--- a/website/source/docs/audit/file.html.md
+++ b/website/source/docs/audit/file.html.md
@@ -85,6 +85,12 @@ Following are the configuration options available for the backend.
Allows selecting the output format. Valid values are `json` (the
default) and `jsonx`, which formats the normal log entries as XML.
+
+ prefix
+ optional
+ Allows a customizable string prefix to write before the actual log
+ line. Defaults to an empty string.
+
diff --git a/website/source/docs/audit/socket.html.md b/website/source/docs/audit/socket.html.md
index 9733e0e420..eb653d38a4 100644
--- a/website/source/docs/audit/socket.html.md
+++ b/website/source/docs/audit/socket.html.md
@@ -74,6 +74,12 @@ Following are the configuration options available for the backend.
optional
Sets the timeout for writes to the socket. Defaults to "2s" (2 seconds).
+
+ prefix
+ optional
+ Allows a customizable string prefix to write before the actual log
+ line. Defaults to an empty string.
+
diff --git a/website/source/docs/audit/syslog.html.md b/website/source/docs/audit/syslog.html.md
index 1078eb695b..22fa39ce81 100644
--- a/website/source/docs/audit/syslog.html.md
+++ b/website/source/docs/audit/syslog.html.md
@@ -71,6 +71,12 @@ Following are the configuration options available for the backend.
Allows selecting the output format. Valid values are `json` (the
default) and `jsonx`, which formats the normal log entries as XML.
+
+ prefix
+ optional
+ Allows a customizable string prefix to write before the actual log
+ line. Defaults to an empty string.
+
diff --git a/website/source/docs/auth/aws-ec2.html.md b/website/source/docs/auth/aws-ec2.html.md
index ea68d6b9f1..30c27b14c3 100644
--- a/website/source/docs/auth/aws-ec2.html.md
+++ b/website/source/docs/auth/aws-ec2.html.md
@@ -24,11 +24,11 @@ familiar with instance metadata, details can be found
One piece of "dynamic metadata" available to the EC2 instance, is the instance
identity document, a JSON representation of a collection of instance metadata.
-Importantly, AWS also provides a copy of this metadata in PKCS#7 format signed
-with its public key, and publishes the public keys used (which are grouped by
-region). (Details on the instance identity document and the signature can be
+AWS also provides PKCS#7 signature of the instance metadata document, and
+publishes the public keys (grouped by region) which can be used to verify the
+signature. Details on the instance identity document and the signature can be
found
-[here](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-identity-documents.html).)
+[here](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-identity-documents.html).
During login, the backend verifies the signature on the PKCS#7 document,
ensuring that the information contained within, is certified accurate by AWS.
diff --git a/website/source/docs/auth/ldap.html.md b/website/source/docs/auth/ldap.html.md
index 43c831911f..7ec754f2a8 100644
--- a/website/source/docs/auth/ldap.html.md
+++ b/website/source/docs/auth/ldap.html.md
@@ -111,7 +111,7 @@ Configuration is written to `auth/ldap/config`.
### Connection parameters
-* `url` (string, required) - The LDAP server to connect to. Examples: `ldap://ldap.myorg.com`, `ldaps://ldap.myorg.com:636`
+* `url` (string, required) - The LDAP server to connect to. Examples: `ldap://ldap.myorg.com`, `ldaps://ldap.myorg.com:636`. This can also be a comma-delineated list of URLs, e.g. `ldap://ldap.myorg.com,ldaps://ldap.myorg.com:636`, in which case the servers will be tried in-order if there are errors during the connection process.
* `starttls` (bool, optional) - If true, issues a `StartTLS` command after establishing an unencrypted connection.
* `insecure_tls` - (bool, optional) - If true, skips LDAP server SSL certificate verification - insecure, use with caution!
* `certificate` - (string, optional) - CA certificate to use when verifying LDAP server certificate, must be x509 PEM encoded.
diff --git a/website/source/docs/config/index.html.md b/website/source/docs/config/index.html.md
index a991178054..7aa1681e7a 100644
--- a/website/source/docs/config/index.html.md
+++ b/website/source/docs/config/index.html.md
@@ -64,8 +64,8 @@ sending a SIGHUP to the server process. These are denoted below.
subsystem. This will very significantly impact performance.
* `disable_mlock` (optional) - A boolean. If true, this will disable the
- server from executing the `mlock` syscall to prevent memory from being
- swapped to disk. This is not recommended in production (see below).
+ server from executing the `mlock` syscall. `mlock` prevents memory from being
+ swapped to disk. Disabling `mlock` is not recommended in production (see below).
* `telemetry` (optional) - Configures the telemetry reporting system
(see below).
diff --git a/website/source/docs/http/libraries.html.md b/website/source/docs/http/libraries.html.md
index 6e33355d12..79c718009b 100644
--- a/website/source/docs/http/libraries.html.md
+++ b/website/source/docs/http/libraries.html.md
@@ -79,6 +79,8 @@ These libraries are provided by the community.
* [vault-php-sdk](https://github.com/jippi/vault-php-sdk)
* `composer require jippi/vault-php-sdk`
+* [vault-php-sdk](https://github.com/violuke/vault-php-sdk) extended from jipppi
+ * `composer require violuke/vault-php-sdk`
### Python
diff --git a/website/source/docs/secrets/transit/index.html.md b/website/source/docs/secrets/transit/index.html.md
index b082d13eec..ea98384271 100644
--- a/website/source/docs/secrets/transit/index.html.md
+++ b/website/source/docs/secrets/transit/index.html.md
@@ -338,7 +338,7 @@ only encrypt or decrypt using the named keys they need access to.
Defaults to 0.
- allow_deletion
+ deletion_allowed
optional
When set, the key is allowed to be deleted. Defaults to false.
diff --git a/website/source/intro/getting-started/secret-backends.html.md b/website/source/intro/getting-started/secret-backends.html.md
index 2e1538ce9e..4e8d90e46d 100644
--- a/website/source/intro/getting-started/secret-backends.html.md
+++ b/website/source/intro/getting-started/secret-backends.html.md
@@ -79,7 +79,7 @@ remains mounted.
```
$ vault unmount generic/
-Successfully unmounted 'generic/'!
+Successfully unmounted 'generic/' if it was mounted
```
In addition to unmounting, you can remount a backend. Remounting a