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