From 5d9aeb7d81902139ca43b0177fcb925930be4191 Mon Sep 17 00:00:00 2001 From: Pier-Hugues Pellerin Date: Fri, 11 Oct 2019 08:24:32 -0400 Subject: [PATCH] Libbeat's HTTP Server can now listen to unix socket. (#13655) * Libbeat's HTTP Server can now listen to unix socket. Allow to use a socket file using the `unix:///tmp/hello.sock` syntax to define an HTTP server that will listen HTTP request. --- CHANGELOG.next.asciidoc | 2 + NOTICE.txt | 3 +- auditbeat/auditbeat.reference.yml | 13 +- filebeat/filebeat.reference.yml | 13 +- heartbeat/heartbeat.reference.yml | 13 +- journalbeat/journalbeat.reference.yml | 13 +- libbeat/_meta/config.reference.yml.tmpl | 13 +- libbeat/api/config.go | 15 +- libbeat/api/make_listener_posix.go | 83 +++++ libbeat/api/make_listener_windows.go | 64 ++++ libbeat/api/npipe/listener_windows.go | 95 ++++++ libbeat/api/npipe/listener_windows_test.go | 73 ++++ libbeat/api/npipe/listerner_posix.go | 33 ++ libbeat/api/npipe/pipe.go | 25 ++ libbeat/api/npipe/pipe_test.go | 35 ++ libbeat/api/routes.go | 75 +++++ libbeat/api/server.go | 111 +++---- libbeat/api/server_test.go | 186 +++++++++++ libbeat/api/server_windows_test.go | 60 ++++ libbeat/cmd/instance/beat.go | 16 +- libbeat/common/seccomp/policy_linux_386.go | 1 + libbeat/common/seccomp/policy_linux_amd64.go | 1 + libbeat/docs/http-endpoint.asciidoc | 13 +- metricbeat/metricbeat.reference.yml | 13 +- packetbeat/packetbeat.reference.yml | 13 +- .../github.com/Microsoft/go-winio/backup.go | 14 +- vendor/github.com/Microsoft/go-winio/ea.go | 137 ++++++++ vendor/github.com/Microsoft/go-winio/file.go | 50 ++- .../github.com/Microsoft/go-winio/fileinfo.go | 3 +- vendor/github.com/Microsoft/go-winio/go.mod | 9 + vendor/github.com/Microsoft/go-winio/go.sum | 16 + .../github.com/Microsoft/go-winio/hvsock.go | 305 +++++++++++++++++ vendor/github.com/Microsoft/go-winio/pipe.go | 314 ++++++++++++------ .../Microsoft/go-winio/pkg/guid/guid.go | 235 +++++++++++++ .../github.com/Microsoft/go-winio/syscall.go | 2 +- .../Microsoft/go-winio/zsyscall_windows.go | 90 +++-- vendor/vendor.json | 32 +- winlogbeat/winlogbeat.reference.yml | 13 +- x-pack/auditbeat/auditbeat.reference.yml | 13 +- x-pack/filebeat/filebeat.reference.yml | 13 +- .../functionbeat/functionbeat.reference.yml | 13 +- x-pack/metricbeat/metricbeat.reference.yml | 13 +- x-pack/winlogbeat/winlogbeat.reference.yml | 13 +- 43 files changed, 2010 insertions(+), 257 deletions(-) create mode 100644 libbeat/api/make_listener_posix.go create mode 100644 libbeat/api/make_listener_windows.go create mode 100644 libbeat/api/npipe/listener_windows.go create mode 100644 libbeat/api/npipe/listener_windows_test.go create mode 100644 libbeat/api/npipe/listerner_posix.go create mode 100644 libbeat/api/npipe/pipe.go create mode 100644 libbeat/api/npipe/pipe_test.go create mode 100644 libbeat/api/routes.go create mode 100644 libbeat/api/server_test.go create mode 100644 libbeat/api/server_windows_test.go create mode 100644 vendor/github.com/Microsoft/go-winio/ea.go create mode 100644 vendor/github.com/Microsoft/go-winio/go.mod create mode 100644 vendor/github.com/Microsoft/go-winio/go.sum create mode 100644 vendor/github.com/Microsoft/go-winio/hvsock.go create mode 100644 vendor/github.com/Microsoft/go-winio/pkg/guid/guid.go diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index 1f7e4488fc5b..8516f6f76140 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -18,6 +18,8 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Update to Golang 1.12.7. {pull}12931[12931] - Remove `in_cluster` configuration parameter for Kuberentes, now in-cluster configuration is used only if no other kubeconfig is specified {pull}13051[13051] - Disable Alibaba Cloud and Tencent Cloud metadata providers by default. {pull}13812[12812] +- Libbeat HTTP's Server can listen to a unix socket using the `unix:///tmp/hello.sock` syntax. {pull}13655[13655] +- Libbeat HTTP's Server can listen to a Windows named pipe using the `npipe:///hello` syntax. {pull}13655[13655] *Auditbeat* diff --git a/NOTICE.txt b/NOTICE.txt index 916decbe1f1b..aa31b55a4dd5 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -4044,7 +4044,8 @@ Copyright 2012 Matt T. Proud (matt.proud@gmail.com) -------------------------------------------------------------------- Dependency: github.com/Microsoft/go-winio -Revision: f533f7a102197536779ea3a8cb881d639e21ec5a +Version: v0.4.14 +Revision: 6c72808b55902eae4c5943626030429ff20f3b63 License type (autodetected): MIT ./vendor/github.com/Microsoft/go-winio/LICENSE: -------------------------------------------------------------------- diff --git a/auditbeat/auditbeat.reference.yml b/auditbeat/auditbeat.reference.yml index 224b0a122116..efd0045d9692 100644 --- a/auditbeat/auditbeat.reference.yml +++ b/auditbeat/auditbeat.reference.yml @@ -1070,7 +1070,7 @@ setup.template.settings: #setup.ilm.enabled: auto # Set the prefix used in the index lifecycle write alias name. The default alias -# name is 'auditbeat-%{[agent.version]}'. +# name is 'auditbeat-%{[agent.version]}'. #setup.ilm.rollover_alias: "auditbeat" # Set the rollover index pattern. The default is "%{now/d}-000001". @@ -1333,12 +1333,21 @@ logging.files: # Defines if the HTTP endpoint is enabled. #http.enabled: false -# The HTTP endpoint will bind to this hostname or IP address. It is recommended to use only localhost. +# The HTTP endpoint will bind to this hostname, IP address, unix socket or named pipe. +# When using IP addresses, it is recommended to only use localhost. #http.host: localhost # Port on which the HTTP endpoint will bind. Default is 5066. #http.port: 5066 +# Define which user should be owning the named pipe. +#http.named_pipe.user: + +# Define which the permissions that should be applied to the named pipe, use the Security +# Descriptor Definition Language (SDDL) to define the permission. This option cannot be used with +# `http.user`. +#http.named_pipe.security_descriptor: + #============================= Process Security ================================ # Enable or disable seccomp system call filtering on Linux. Default is enabled. diff --git a/filebeat/filebeat.reference.yml b/filebeat/filebeat.reference.yml index f58ac33ebf33..1a4737ff17ae 100644 --- a/filebeat/filebeat.reference.yml +++ b/filebeat/filebeat.reference.yml @@ -1771,7 +1771,7 @@ setup.template.settings: #setup.ilm.enabled: auto # Set the prefix used in the index lifecycle write alias name. The default alias -# name is 'filebeat-%{[agent.version]}'. +# name is 'filebeat-%{[agent.version]}'. #setup.ilm.rollover_alias: "filebeat" # Set the rollover index pattern. The default is "%{now/d}-000001". @@ -2034,12 +2034,21 @@ logging.files: # Defines if the HTTP endpoint is enabled. #http.enabled: false -# The HTTP endpoint will bind to this hostname or IP address. It is recommended to use only localhost. +# The HTTP endpoint will bind to this hostname, IP address, unix socket or named pipe. +# When using IP addresses, it is recommended to only use localhost. #http.host: localhost # Port on which the HTTP endpoint will bind. Default is 5066. #http.port: 5066 +# Define which user should be owning the named pipe. +#http.named_pipe.user: + +# Define which the permissions that should be applied to the named pipe, use the Security +# Descriptor Definition Language (SDDL) to define the permission. This option cannot be used with +# `http.user`. +#http.named_pipe.security_descriptor: + #============================= Process Security ================================ # Enable or disable seccomp system call filtering on Linux. Default is enabled. diff --git a/heartbeat/heartbeat.reference.yml b/heartbeat/heartbeat.reference.yml index a9f9de1b54bf..4cb71a1d7841 100644 --- a/heartbeat/heartbeat.reference.yml +++ b/heartbeat/heartbeat.reference.yml @@ -1214,7 +1214,7 @@ setup.template.settings: #setup.ilm.enabled: auto # Set the prefix used in the index lifecycle write alias name. The default alias -# name is 'heartbeat-%{[agent.version]}'. +# name is 'heartbeat-%{[agent.version]}'. #setup.ilm.rollover_alias: "heartbeat" # Set the rollover index pattern. The default is "%{now/d}-000001". @@ -1477,12 +1477,21 @@ logging.files: # Defines if the HTTP endpoint is enabled. #http.enabled: false -# The HTTP endpoint will bind to this hostname or IP address. It is recommended to use only localhost. +# The HTTP endpoint will bind to this hostname, IP address, unix socket or named pipe. +# When using IP addresses, it is recommended to only use localhost. #http.host: localhost # Port on which the HTTP endpoint will bind. Default is 5066. #http.port: 5066 +# Define which user should be owning the named pipe. +#http.named_pipe.user: + +# Define which the permissions that should be applied to the named pipe, use the Security +# Descriptor Definition Language (SDDL) to define the permission. This option cannot be used with +# `http.user`. +#http.named_pipe.security_descriptor: + #============================= Process Security ================================ # Enable or disable seccomp system call filtering on Linux. Default is enabled. diff --git a/journalbeat/journalbeat.reference.yml b/journalbeat/journalbeat.reference.yml index 0aa4c2c242de..a7a768e8a691 100644 --- a/journalbeat/journalbeat.reference.yml +++ b/journalbeat/journalbeat.reference.yml @@ -1015,7 +1015,7 @@ setup.template.settings: #setup.ilm.enabled: auto # Set the prefix used in the index lifecycle write alias name. The default alias -# name is 'journalbeat-%{[agent.version]}'. +# name is 'journalbeat-%{[agent.version]}'. #setup.ilm.rollover_alias: "journalbeat" # Set the rollover index pattern. The default is "%{now/d}-000001". @@ -1278,12 +1278,21 @@ logging.files: # Defines if the HTTP endpoint is enabled. #http.enabled: false -# The HTTP endpoint will bind to this hostname or IP address. It is recommended to use only localhost. +# The HTTP endpoint will bind to this hostname, IP address, unix socket or named pipe. +# When using IP addresses, it is recommended to only use localhost. #http.host: localhost # Port on which the HTTP endpoint will bind. Default is 5066. #http.port: 5066 +# Define which user should be owning the named pipe. +#http.named_pipe.user: + +# Define which the permissions that should be applied to the named pipe, use the Security +# Descriptor Definition Language (SDDL) to define the permission. This option cannot be used with +# `http.user`. +#http.named_pipe.security_descriptor: + #============================= Process Security ================================ # Enable or disable seccomp system call filtering on Linux. Default is enabled. diff --git a/libbeat/_meta/config.reference.yml.tmpl b/libbeat/_meta/config.reference.yml.tmpl index 70b64a598fb8..af90f66f1e76 100644 --- a/libbeat/_meta/config.reference.yml.tmpl +++ b/libbeat/_meta/config.reference.yml.tmpl @@ -958,7 +958,7 @@ setup.template.settings: #setup.ilm.enabled: auto # Set the prefix used in the index lifecycle write alias name. The default alias -# name is 'beatname-%{[agent.version]}'. +# name is 'beatname-%{[agent.version]}'. #setup.ilm.rollover_alias: "beat-index-prefix" # Set the rollover index pattern. The default is "%{now/d}-000001". @@ -1221,12 +1221,21 @@ logging.files: # Defines if the HTTP endpoint is enabled. #http.enabled: false -# The HTTP endpoint will bind to this hostname or IP address. It is recommended to use only localhost. +# The HTTP endpoint will bind to this hostname, IP address, unix socket or named pipe. +# When using IP addresses, it is recommended to only use localhost. #http.host: localhost # Port on which the HTTP endpoint will bind. Default is 5066. #http.port: 5066 +# Define which user should be owning the named pipe. +#http.named_pipe.user: + +# Define which the permissions that should be applied to the named pipe, use the Security +# Descriptor Definition Language (SDDL) to define the permission. This option cannot be used with +# `http.user`. +#http.named_pipe.security_descriptor: + #============================= Process Security ================================ # Enable or disable seccomp system call filtering on Linux. Default is enabled. diff --git a/libbeat/api/config.go b/libbeat/api/config.go index 0ba306345c75..f6e57551b360 100644 --- a/libbeat/api/config.go +++ b/libbeat/api/config.go @@ -17,16 +17,25 @@ package api +import "os" + +// Config is the configuration for the API endpoint. type Config struct { - Enabled bool - Host string - Port int + Enabled bool `config:"enabled"` + Host string `config:"host"` + Port int `config:"port"` + User string `config:"named_pipe.user"` + SecurityDescriptor string `config:"named_pipe.security_descriptor"` } var ( + // DefaultConfig is the default configuration used by the API endpoint. DefaultConfig = Config{ Enabled: false, Host: "localhost", Port: 5066, } ) + +// File mode for the socket file, owner of the process can do everything, member of the group can read. +const socketFileMode = os.FileMode(0740) diff --git a/libbeat/api/make_listener_posix.go b/libbeat/api/make_listener_posix.go new file mode 100644 index 000000000000..672af5cc95df --- /dev/null +++ b/libbeat/api/make_listener_posix.go @@ -0,0 +1,83 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +//+build !windows + +package api + +import ( + "fmt" + "net" + "os" + + "github.com/pkg/errors" + + "github.com/elastic/beats/libbeat/api/npipe" +) + +func makeListener(cfg Config) (net.Listener, error) { + if len(cfg.User) > 0 { + return nil, errors.New("specifying a user is not supported under this platform") + } + + if len(cfg.SecurityDescriptor) > 0 { + return nil, errors.New("security_descriptor option for the HTTP endpoint only work on Windows") + } + + if npipe.IsNPipe(cfg.Host) { + return nil, fmt.Errorf( + "cannot use %s as the host, named pipes are only supported on Windows", + cfg.Host, + ) + } + + network, path, err := parse(cfg.Host, cfg.Port) + if err != nil { + return nil, err + } + + if network == "unix" { + if _, err := os.Stat(path); !os.IsNotExist(err) { + if err := os.Remove(path); err != nil { + return nil, errors.Wrapf( + err, + "cannot remove existing unix socket file at location %s", + path, + ) + } + } + } + + l, err := net.Listen(network, path) + if err != nil { + return nil, err + } + + // Ensure file mode + if network == "unix" { + if err := os.Chmod(path, socketFileMode); err != nil { + return nil, errors.Wrapf( + err, + "could not set mode %d for unix socket file at location %s", + socketFileMode, + path, + ) + } + } + + return l, nil +} diff --git a/libbeat/api/make_listener_windows.go b/libbeat/api/make_listener_windows.go new file mode 100644 index 000000000000..ef549f46a9af --- /dev/null +++ b/libbeat/api/make_listener_windows.go @@ -0,0 +1,64 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +//+build windows + +package api + +import ( + "fmt" + "net" + + "github.com/pkg/errors" + + "github.com/elastic/beats/libbeat/api/npipe" +) + +func makeListener(cfg Config) (net.Listener, error) { + if len(cfg.User) > 0 && len(cfg.SecurityDescriptor) > 0 { + return nil, errors.New("user and security_descriptor are mutually exclusive, define only one of them") + } + + if npipe.IsNPipe(cfg.Host) { + pipe := npipe.TransformString(cfg.Host) + var sd string + var err error + if len(cfg.SecurityDescriptor) == 0 { + sd, err = npipe.DefaultSD(cfg.User) + if err != nil { + return nil, errors.Wrap(err, "cannot generate security descriptor for the named pipe") + } + } else { + sd = cfg.SecurityDescriptor + } + return npipe.NewListener(pipe, sd) + } + + network, path, err := parse(cfg.Host, cfg.Port) + if err != nil { + return nil, err + } + + if network == "unix" { + return nil, fmt.Errorf( + "cannot use %s as the host, unix sockets are not supported on Windows, use npipe instead", + cfg.Host, + ) + } + + return net.Listen(network, path) +} diff --git a/libbeat/api/npipe/listener_windows.go b/libbeat/api/npipe/listener_windows.go new file mode 100644 index 000000000000..c3c9db7a7196 --- /dev/null +++ b/libbeat/api/npipe/listener_windows.go @@ -0,0 +1,95 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// +build windows + +package npipe + +import ( + "context" + "net" + "os/user" + "strings" + + winio "github.com/Microsoft/go-winio" + "github.com/pkg/errors" +) + +// NewListener creates a new Listener receiving events over a named pipe. +func NewListener(name, sd string) (net.Listener, error) { + c := &winio.PipeConfig{ + SecurityDescriptor: sd, + } + + l, err := winio.ListenPipe(name, c) + if err != nil { + return nil, errors.Wrapf(err, "failed to listen on the named pipe %s", name) + } + + return l, nil +} + +// TransformString takes an input type name defined as a URI like `npipe:///hello` and transform it into +// `\\.\pipe\hello` +func TransformString(name string) string { + if strings.HasPrefix(name, "npipe:///") { + path := strings.TrimPrefix(name, "npipe:///") + return `\\.\pipe\` + path + } + + if strings.HasPrefix(name, `\\.\pipe\`) { + return name + } + + return name +} + +// DialContext create a Dial to be use with an http.Client to connect to a pipe. +func DialContext(npipe string) func(context.Context, string, string) (net.Conn, error) { + return func(ctx context.Context, _, _ string) (net.Conn, error) { + return winio.DialPipeContext(ctx, npipe) + } +} + +// DefaultSD returns a default SecurityDescriptor which is the minimal required permissions to be +// able to write to the named pipe. The security descriptor is returned in SDDL format. +// +// Docs: https://docs.microsoft.com/en-us/windows/win32/secauthz/security-descriptor-string-format +func DefaultSD(forUser string) (string, error) { + var u *user.User + var err error + // No user configured we fallback to the current running user. + if len(forUser) == 0 { + u, err = user.Current() + if err != nil { + return "", errors.Wrap(err, "failed to retrieve the current user") + } + } else { + u, err = user.Lookup(forUser) + if err != nil { + return "", errors.Wrapf(err, "failed to retrieve the user %s", forUser) + } + } + + // Named pipe security and access rights. + // We create the pipe and the specific users should only be able to write to it. + // See docs: https://docs.microsoft.com/en-us/windows/win32/ipc/named-pipe-security-and-access-rights + // String definition: https://docs.microsoft.com/en-us/windows/win32/secauthz/ace-strings + // Give generic read/write access to the specified user. + descriptor := "D:P(A;;GA;;;" + u.Uid + ")" + return descriptor, nil +} diff --git a/libbeat/api/npipe/listener_windows_test.go b/libbeat/api/npipe/listener_windows_test.go new file mode 100644 index 000000000000..486bb832d340 --- /dev/null +++ b/libbeat/api/npipe/listener_windows_test.go @@ -0,0 +1,73 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// +build windows + +package npipe + +import ( + "fmt" + "io/ioutil" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestHTTPOverNamedPipe(t *testing.T) { + sd, err := DefaultSD("") + require.NoError(t, err) + npipe := TransformString("npipe:///hello-world") + l, err := NewListener(npipe, sd) + require.NoError(t, err) + defer l.Close() + + mux := http.NewServeMux() + mux.HandleFunc("/echo-hello", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "ehlo!") + }) + + go http.Serve(l, mux) + + c := http.Client{ + Transport: &http.Transport{ + DialContext: DialContext(npipe), + }, + } + + r, err := c.Get("http://npipe/echo-hello") + require.NoError(t, err) + body, err := ioutil.ReadAll(r.Body) + defer r.Body.Close() + + assert.Equal(t, "ehlo!", string(body)) +} + +func TestTransformString(t *testing.T) { + t.Run("with npipe:// scheme", func(t *testing.T) { + assert.Equal(t, `\\.\pipe\hello`, TransformString("npipe:///hello")) + }) + + t.Run("with windows pipe syntax", func(t *testing.T) { + assert.Equal(t, `\\.\pipe\hello`, TransformString(`\\.\pipe\hello`)) + }) + + t.Run("everything else", func(t *testing.T) { + assert.Equal(t, "hello", TransformString("hello")) + }) +} diff --git a/libbeat/api/npipe/listerner_posix.go b/libbeat/api/npipe/listerner_posix.go new file mode 100644 index 000000000000..c8239fd914ad --- /dev/null +++ b/libbeat/api/npipe/listerner_posix.go @@ -0,0 +1,33 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +//+build !windows + +package npipe + +import ( + "context" + "errors" + "net" +) + +// DialContext create a Dial to be use with an http.Client to connect to a pipe. +func DialContext(npipe string) func(context.Context, string, string) (net.Conn, error) { + return func(ctx context.Context, _, _ string) (net.Conn, error) { + return nil, errors.New("named pipe doesn't work on linux") + } +} diff --git a/libbeat/api/npipe/pipe.go b/libbeat/api/npipe/pipe.go new file mode 100644 index 000000000000..d7cbc44e102d --- /dev/null +++ b/libbeat/api/npipe/pipe.go @@ -0,0 +1,25 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package npipe + +import "strings" + +// IsNPipe returns true if the string has a npipe scheme. +func IsNPipe(s string) bool { + return strings.HasPrefix(s, "npipe:///") || strings.HasPrefix(s, `\\.\pipe\`) +} diff --git a/libbeat/api/npipe/pipe_test.go b/libbeat/api/npipe/pipe_test.go new file mode 100644 index 000000000000..f0c3465e44a7 --- /dev/null +++ b/libbeat/api/npipe/pipe_test.go @@ -0,0 +1,35 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package npipe + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestIsNPipe(t *testing.T) { + t.Run("return true on named pipe", func(t *testing.T) { + assert.True(t, IsNPipe("npipe:///hello")) + assert.True(t, IsNPipe(`\\.\pipe\hello`)) + }) + + t.Run("return false if its not a named pipe", func(t *testing.T) { + assert.False(t, IsNPipe("unix:///tmp/ok.sock")) + }) +} diff --git a/libbeat/api/routes.go b/libbeat/api/routes.go new file mode 100644 index 000000000000..e78dfa16ae84 --- /dev/null +++ b/libbeat/api/routes.go @@ -0,0 +1,75 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package api + +import ( + "fmt" + "net/http" + "net/url" + + "github.com/elastic/beats/libbeat/common" + "github.com/elastic/beats/libbeat/logp" + "github.com/elastic/beats/libbeat/monitoring" +) + +type handlerFunc func(http.ResponseWriter, *http.Request) +type lookupFunc func(string) *monitoring.Namespace + +// NewWithDefaultRoutes creates a new server with default API routes. +func NewWithDefaultRoutes(log *logp.Logger, config *common.Config, ns lookupFunc) (*Server, error) { + mux := http.NewServeMux() + + mux.HandleFunc("/", makeRootAPIHandler(makeAPIHandler(ns("info")))) + mux.HandleFunc("/state", makeAPIHandler(ns("state"))) + mux.HandleFunc("/stats", makeAPIHandler(ns("stats"))) + mux.HandleFunc("/dataset", makeAPIHandler(ns("dataset"))) + return New(log, mux, config) +} + +func makeRootAPIHandler(handler handlerFunc) handlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/" { + http.NotFound(w, r) + return + } + handler(w, r) + } +} + +func makeAPIHandler(ns *monitoring.Namespace) handlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + + data := monitoring.CollectStructSnapshot( + ns.GetRegistry(), + monitoring.Full, + false, + ) + + prettyPrint(w, data, r.URL) + } +} + +func prettyPrint(w http.ResponseWriter, data common.MapStr, u *url.URL) { + query := u.Query() + if _, ok := query["pretty"]; ok { + fmt.Fprintf(w, data.StringToPrint()) + } else { + fmt.Fprintf(w, data.String()) + } +} diff --git a/libbeat/api/server.go b/libbeat/api/server.go index 3280ccebc8fb..44db0baed145 100644 --- a/libbeat/api/server.go +++ b/libbeat/api/server.go @@ -19,86 +19,77 @@ package api import ( "fmt" + "net" "net/http" "net/url" "strconv" "github.com/elastic/beats/libbeat/common" - "github.com/elastic/beats/libbeat/common/cfgwarn" "github.com/elastic/beats/libbeat/logp" - "github.com/elastic/beats/libbeat/monitoring" ) -// Start starts the metrics api endpoint on the configured host and port -func Start(cfg *common.Config) { - cfgwarn.Experimental("Metrics endpoint is enabled.") - config := DefaultConfig - cfg.Unpack(&config) - - logp.Info("Starting stats endpoint") - go func() { - mux := http.NewServeMux() - - // register handlers - mux.HandleFunc("/", rootHandler()) - mux.HandleFunc("/state", stateHandler) - mux.HandleFunc("/stats", statsHandler) - mux.HandleFunc("/dataset", datasetHandler) - - url := config.Host + ":" + strconv.Itoa(config.Port) - logp.Info("Metrics endpoint listening on: %s", url) - endpoint := http.ListenAndServe(url, mux) - logp.Info("finished starting stats endpoint: %v", endpoint) - }() +// Server takes cares of correctly starting the HTTP component of the API +// and will answers all the routes defined in the received ServeMux. +type Server struct { + log *logp.Logger + mux *http.ServeMux + l net.Listener + config Config } -func rootHandler() func(http.ResponseWriter, *http.Request) { - return func(w http.ResponseWriter, r *http.Request) { - // Return error page - if r.URL.Path != "/" { - http.NotFound(w, r) - return - } - - w.Header().Set("Content-Type", "application/json; charset=utf-8") - - data := monitoring.CollectStructSnapshot(monitoring.GetNamespace("info").GetRegistry(), monitoring.Full, false) - - print(w, data, r.URL) +// New creates a new API Server. +func New(log *logp.Logger, mux *http.ServeMux, config *common.Config) (*Server, error) { + if log == nil { + log = logp.NewLogger("") } -} -// stateHandler reports state metrics -func stateHandler(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json; charset=utf-8") + cfg := DefaultConfig + err := config.Unpack(&cfg) + if err != nil { + return nil, err + } - data := monitoring.CollectStructSnapshot(monitoring.GetNamespace("state").GetRegistry(), monitoring.Full, false) + l, err := makeListener(cfg) + if err != nil { + return nil, err + } - print(w, data, r.URL) + return &Server{mux: mux, l: l, config: cfg, log: log.Named("api")}, nil } -// statsHandler report expvar and all libbeat/monitoring metrics -func statsHandler(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json; charset=utf-8") - - data := monitoring.CollectStructSnapshot(monitoring.GetNamespace("stats").GetRegistry(), monitoring.Full, false) - - print(w, data, r.URL) +// Start starts the HTTP server and accepting new connection. +func (s *Server) Start() { + s.log.Info("Starting stats endpoint") + go func(l net.Listener) { + s.log.Infof("Metrics endpoint listening on: %s (configured: %s)", l.Addr().String(), s.config.Host) + http.Serve(l, s.mux) + s.log.Infof("Finished starting stats endpoint: %s", l.Addr().String()) + }(s.l) } -func datasetHandler(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json; charset=utf-8") +// Stop stops the API server and free any resource associated with the process like unix sockets. +func (s *Server) Stop() error { + return s.l.Close() +} - data := monitoring.CollectStructSnapshot(monitoring.GetNamespace("dataset").GetRegistry(), monitoring.Full, false) +func parse(host string, port int) (string, string, error) { + url, err := url.Parse(host) + if err != nil { + return "", "", err + } - print(w, data, r.URL) -} + // When you don't explicitely define the Scheme we fallback on tcp + host. + if len(url.Host) == 0 && len(url.Scheme) == 0 { + addr := host + ":" + strconv.Itoa(port) + return "tcp", addr, nil + } -func print(w http.ResponseWriter, data common.MapStr, u *url.URL) { - query := u.Query() - if _, ok := query["pretty"]; ok { - fmt.Fprintf(w, data.StringToPrint()) - } else { - fmt.Fprintf(w, data.String()) + switch url.Scheme { + case "http": + return "tcp", url.Host, nil + case "unix": + return url.Scheme, url.Path, nil + default: + return "", "", fmt.Errorf("unknown scheme %s for host string %s", url.Scheme, host) } } diff --git a/libbeat/api/server_test.go b/libbeat/api/server_test.go new file mode 100644 index 000000000000..c021113ccb13 --- /dev/null +++ b/libbeat/api/server_test.go @@ -0,0 +1,186 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package api + +import ( + "context" + "fmt" + "io/ioutil" + "net" + "net/http" + "os" + "runtime" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/elastic/beats/libbeat/common" +) + +func TestConfiguration(t *testing.T) { + if runtime.GOOS != "windows" { + t.Skip("Check for User and Security Descriptor") + return + } + t.Run("when user is set", func(t *testing.T) { + cfg := common.MustNewConfigFrom(map[string]interface{}{ + "host": "unix:///tmp/ok", + "user": "admin", + }) + + _, err := New(nil, simpleMux(), cfg) + assert.Equal(t, err == nil, false) + }) + + t.Run("when security descriptor is set", func(t *testing.T) { + cfg := common.MustNewConfigFrom(map[string]interface{}{ + "host": "unix:///tmp/ok", + "security_descriptor": "D:P(A;;GA;;;1234)", + }) + + _, err := New(nil, simpleMux(), cfg) + assert.Equal(t, err == nil, false) + }) +} + +func TestSocket(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Unix Sockets don't work under windows") + return + } + + client := func(sockFile string) http.Client { + return http.Client{ + Transport: &http.Transport{ + DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { + return net.Dial("unix", sockFile) + }, + }, + } + } + + t.Run("socket doesn't exist before", func(t *testing.T) { + tmpDir, err := ioutil.TempDir("", "testsocket") + require.NoError(t, err) + defer os.RemoveAll(tmpDir) + + sockFile := tmpDir + "/test.sock" + + cfg := common.MustNewConfigFrom(map[string]interface{}{ + "host": "unix://" + sockFile, + }) + + s, err := New(nil, simpleMux(), cfg) + require.NoError(t, err) + go s.Start() + defer func() { + s.Stop() + // Make we cleanup behind + _, err := os.Stat(sockFile) + require.Error(t, err) + require.False(t, os.IsExist(err)) + }() + + c := client(sockFile) + + r, err := c.Get("http://unix/echo-hello") + require.NoError(t, err) + defer r.Body.Close() + + body, err := ioutil.ReadAll(r.Body) + require.NoError(t, err) + + assert.Equal(t, "ehlo!", string(body)) + fi, err := os.Stat(sockFile) + assert.Equal(t, socketFileMode, fi.Mode().Perm()) + }) + + t.Run("starting beat and recover a dangling socket file", func(t *testing.T) { + tmpDir, err := ioutil.TempDir("", "testsocket") + require.NoError(t, err) + defer os.RemoveAll(tmpDir) + + sockFile := tmpDir + "/test.sock" + + // Create the socket before the server. + f, err := os.Create(sockFile) + require.NoError(t, err) + f.Close() + + cfg := common.MustNewConfigFrom(map[string]interface{}{ + "host": "unix://" + sockFile, + }) + + s, err := New(nil, simpleMux(), cfg) + require.NoError(t, err) + go s.Start() + defer func() { + s.Stop() + // Make we cleanup behind + _, err := os.Stat(sockFile) + require.Error(t, err) + require.False(t, os.IsExist(err)) + }() + + c := client(sockFile) + + r, err := c.Get("http://unix/echo-hello") + require.NoError(t, err) + defer r.Body.Close() + + body, err := ioutil.ReadAll(r.Body) + require.NoError(t, err) + + assert.Equal(t, "ehlo!", string(body)) + + fi, err := os.Stat(sockFile) + assert.Equal(t, socketFileMode, fi.Mode().Perm(), "incorrect mode for file %s", sockFile) + }) +} + +func TestHTTP(t *testing.T) { + // select a random free port. + url := "http://localhost:0" + + cfg := common.MustNewConfigFrom(map[string]interface{}{ + "host": url, + }) + + s, err := New(nil, simpleMux(), cfg) + require.NoError(t, err) + go s.Start() + defer s.Stop() + + r, err := http.Get("http://" + s.l.Addr().String() + "/echo-hello") + require.NoError(t, err) + defer r.Body.Close() + + body, err := ioutil.ReadAll(r.Body) + require.NoError(t, err) + + assert.Equal(t, "ehlo!", string(body)) +} + +func simpleMux() *http.ServeMux { + mux := http.NewServeMux() + mux.HandleFunc("/echo-hello", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "ehlo!") + }) + return mux +} diff --git a/libbeat/api/server_windows_test.go b/libbeat/api/server_windows_test.go new file mode 100644 index 000000000000..0931da865c33 --- /dev/null +++ b/libbeat/api/server_windows_test.go @@ -0,0 +1,60 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +//+build windows + +package api + +import ( + "io/ioutil" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/elastic/beats/libbeat/api/npipe" + "github.com/elastic/beats/libbeat/common" +) + +func TestNamedPipe(t *testing.T) { + p := "npipe:///hello" + + cfg := common.MustNewConfigFrom(map[string]interface{}{ + "host": p, + }) + + s, err := New(nil, simpleMux(), cfg) + require.NoError(t, err) + go s.Start() + defer s.Stop() + + c := http.Client{ + Transport: &http.Transport{ + DialContext: npipe.DialContext(npipe.TransformString(p)), + }, + } + + r, err := c.Get("http://npipe/echo-hello") + require.NoError(t, err) + defer r.Body.Close() + + body, err := ioutil.ReadAll(r.Body) + require.NoError(t, err) + + assert.Equal(t, "ehlo!", string(body)) +} diff --git a/libbeat/cmd/instance/beat.go b/libbeat/cmd/instance/beat.go index 3a7a47cbe411..6a2107d8eef8 100644 --- a/libbeat/cmd/instance/beat.go +++ b/libbeat/cmd/instance/beat.go @@ -376,6 +376,18 @@ func (b *Beat) launch(settings Settings, bt beat.Creator) error { svc.BeforeRun() defer svc.Cleanup() + // Start the API Server before the Seccomp lock down, we do this so we can create the unix socket + // set the appropriate permission on the unix domain file without having to whitelist anything + // that would be set at runtime. + if b.Config.HTTP.Enabled() { + s, err := api.NewWithDefaultRoutes(logp.NewLogger(""), b.Config.HTTP, monitoring.GetNamespace) + if err != nil { + return errw.Wrap(err, "could not start the HTTP server for the API") + } + s.Start() + defer s.Stop() + } + if err = seccomp.LoadFilter(b.Config.Seccomp); err != nil { return err } @@ -429,10 +441,6 @@ func (b *Beat) launch(settings Settings, bt beat.Creator) error { logp.Info("%s start running.", b.Info.Beat) - if b.Config.HTTP.Enabled() { - api.Start(b.Config.HTTP) - } - // Launch config manager b.ConfigManager.Start() defer b.ConfigManager.Stop() diff --git a/libbeat/common/seccomp/policy_linux_386.go b/libbeat/common/seccomp/policy_linux_386.go index 3f08248958a2..76b24714cac2 100644 --- a/libbeat/common/seccomp/policy_linux_386.go +++ b/libbeat/common/seccomp/policy_linux_386.go @@ -31,6 +31,7 @@ func init() { "_llseek", "access", "brk", + "chmod", "clock_gettime", "clone", "close", diff --git a/libbeat/common/seccomp/policy_linux_amd64.go b/libbeat/common/seccomp/policy_linux_amd64.go index a131e7f3c344..92b5fbe488a5 100644 --- a/libbeat/common/seccomp/policy_linux_amd64.go +++ b/libbeat/common/seccomp/policy_linux_amd64.go @@ -34,6 +34,7 @@ func init() { "arch_prctl", "bind", "brk", + "chmod", "clock_gettime", "clone", "close", diff --git a/libbeat/docs/http-endpoint.asciidoc b/libbeat/docs/http-endpoint.asciidoc index 54e0944002ce..e60a0abbe723 100644 --- a/libbeat/docs/http-endpoint.asciidoc +++ b/libbeat/docs/http-endpoint.asciidoc @@ -21,12 +21,23 @@ by default, as you may want to avoid exposing this info. The HTTP endpoint has the following configuration settings: `http.enabled`:: (Optional) Enable the HTTP endpoint. Default is `false`. -`http.host`:: (Optional) Bind to this hostname or IP address. +`http.host`:: (Optional) Bind to this hostname, IP address, unix socket (unix:///var/run/{beatname_lc}.sock) or Windows named pipe (npipe:///{beatname_lc}). It is recommended to use only localhost. Default is `localhost` `http.port`:: (Optional) Port on which the HTTP endpoint will bind. Default is `5066`. +`http.named_pipe.user`:: (Optional) User to use to create the named pipe, only work on Windows, Default to the +current user. +`http.named_pipe.security_descriptor`:: (Optional) Windows Security descriptor string defined in the SDDL format. Default to +read and write permission for the current user. This is the list of paths you can access. For pretty JSON output append ?pretty to the URL. +You can query a unix socket using the `CURL` command and the `--unix-socket` flag. + +[source,js] +---- +curl -XGET --unix-socket '/var/run/{beatname_lc}.sock' 'http://unix/stats/?pretty' +---- + [float] === Info diff --git a/metricbeat/metricbeat.reference.yml b/metricbeat/metricbeat.reference.yml index 681e4c0a534d..db2d8946dc6d 100644 --- a/metricbeat/metricbeat.reference.yml +++ b/metricbeat/metricbeat.reference.yml @@ -1734,7 +1734,7 @@ setup.template.settings: #setup.ilm.enabled: auto # Set the prefix used in the index lifecycle write alias name. The default alias -# name is 'metricbeat-%{[agent.version]}'. +# name is 'metricbeat-%{[agent.version]}'. #setup.ilm.rollover_alias: "metricbeat" # Set the rollover index pattern. The default is "%{now/d}-000001". @@ -1997,12 +1997,21 @@ logging.files: # Defines if the HTTP endpoint is enabled. #http.enabled: false -# The HTTP endpoint will bind to this hostname or IP address. It is recommended to use only localhost. +# The HTTP endpoint will bind to this hostname, IP address, unix socket or named pipe. +# When using IP addresses, it is recommended to only use localhost. #http.host: localhost # Port on which the HTTP endpoint will bind. Default is 5066. #http.port: 5066 +# Define which user should be owning the named pipe. +#http.named_pipe.user: + +# Define which the permissions that should be applied to the named pipe, use the Security +# Descriptor Definition Language (SDDL) to define the permission. This option cannot be used with +# `http.user`. +#http.named_pipe.security_descriptor: + #============================= Process Security ================================ # Enable or disable seccomp system call filtering on Linux. Default is enabled. diff --git a/packetbeat/packetbeat.reference.yml b/packetbeat/packetbeat.reference.yml index d520eb5ca242..4ce14a879b2a 100644 --- a/packetbeat/packetbeat.reference.yml +++ b/packetbeat/packetbeat.reference.yml @@ -1447,7 +1447,7 @@ setup.template.settings: #setup.ilm.enabled: auto # Set the prefix used in the index lifecycle write alias name. The default alias -# name is 'packetbeat-%{[agent.version]}'. +# name is 'packetbeat-%{[agent.version]}'. #setup.ilm.rollover_alias: "packetbeat" # Set the rollover index pattern. The default is "%{now/d}-000001". @@ -1710,12 +1710,21 @@ logging.files: # Defines if the HTTP endpoint is enabled. #http.enabled: false -# The HTTP endpoint will bind to this hostname or IP address. It is recommended to use only localhost. +# The HTTP endpoint will bind to this hostname, IP address, unix socket or named pipe. +# When using IP addresses, it is recommended to only use localhost. #http.host: localhost # Port on which the HTTP endpoint will bind. Default is 5066. #http.port: 5066 +# Define which user should be owning the named pipe. +#http.named_pipe.user: + +# Define which the permissions that should be applied to the named pipe, use the Security +# Descriptor Definition Language (SDDL) to define the permission. This option cannot be used with +# `http.user`. +#http.named_pipe.security_descriptor: + #============================= Process Security ================================ # Enable or disable seccomp system call filtering on Linux. Default is enabled. diff --git a/vendor/github.com/Microsoft/go-winio/backup.go b/vendor/github.com/Microsoft/go-winio/backup.go index 27d6ace0c910..2be34af43106 100644 --- a/vendor/github.com/Microsoft/go-winio/backup.go +++ b/vendor/github.com/Microsoft/go-winio/backup.go @@ -68,10 +68,20 @@ func NewBackupStreamReader(r io.Reader) *BackupStreamReader { return &BackupStreamReader{r, 0} } -// Next returns the next backup stream and prepares for calls to Write(). It skips the remainder of the current stream if +// Next returns the next backup stream and prepares for calls to Read(). It skips the remainder of the current stream if // it was not completely read. func (r *BackupStreamReader) Next() (*BackupHeader, error) { if r.bytesLeft > 0 { + if s, ok := r.r.(io.Seeker); ok { + // Make sure Seek on io.SeekCurrent sometimes succeeds + // before trying the actual seek. + if _, err := s.Seek(0, io.SeekCurrent); err == nil { + if _, err = s.Seek(r.bytesLeft, io.SeekCurrent); err != nil { + return nil, err + } + r.bytesLeft = 0 + } + } if _, err := io.Copy(ioutil.Discard, r); err != nil { return nil, err } @@ -220,7 +230,7 @@ type BackupFileWriter struct { ctx uintptr } -// NewBackupFileWrtier returns a new BackupFileWriter from a file handle. If includeSecurity is true, +// NewBackupFileWriter returns a new BackupFileWriter from a file handle. If includeSecurity is true, // Write() will attempt to restore the security descriptor from the stream. func NewBackupFileWriter(f *os.File, includeSecurity bool) *BackupFileWriter { w := &BackupFileWriter{f, includeSecurity, 0} diff --git a/vendor/github.com/Microsoft/go-winio/ea.go b/vendor/github.com/Microsoft/go-winio/ea.go new file mode 100644 index 000000000000..4051c1b33bfe --- /dev/null +++ b/vendor/github.com/Microsoft/go-winio/ea.go @@ -0,0 +1,137 @@ +package winio + +import ( + "bytes" + "encoding/binary" + "errors" +) + +type fileFullEaInformation struct { + NextEntryOffset uint32 + Flags uint8 + NameLength uint8 + ValueLength uint16 +} + +var ( + fileFullEaInformationSize = binary.Size(&fileFullEaInformation{}) + + errInvalidEaBuffer = errors.New("invalid extended attribute buffer") + errEaNameTooLarge = errors.New("extended attribute name too large") + errEaValueTooLarge = errors.New("extended attribute value too large") +) + +// ExtendedAttribute represents a single Windows EA. +type ExtendedAttribute struct { + Name string + Value []byte + Flags uint8 +} + +func parseEa(b []byte) (ea ExtendedAttribute, nb []byte, err error) { + var info fileFullEaInformation + err = binary.Read(bytes.NewReader(b), binary.LittleEndian, &info) + if err != nil { + err = errInvalidEaBuffer + return + } + + nameOffset := fileFullEaInformationSize + nameLen := int(info.NameLength) + valueOffset := nameOffset + int(info.NameLength) + 1 + valueLen := int(info.ValueLength) + nextOffset := int(info.NextEntryOffset) + if valueLen+valueOffset > len(b) || nextOffset < 0 || nextOffset > len(b) { + err = errInvalidEaBuffer + return + } + + ea.Name = string(b[nameOffset : nameOffset+nameLen]) + ea.Value = b[valueOffset : valueOffset+valueLen] + ea.Flags = info.Flags + if info.NextEntryOffset != 0 { + nb = b[info.NextEntryOffset:] + } + return +} + +// DecodeExtendedAttributes decodes a list of EAs from a FILE_FULL_EA_INFORMATION +// buffer retrieved from BackupRead, ZwQueryEaFile, etc. +func DecodeExtendedAttributes(b []byte) (eas []ExtendedAttribute, err error) { + for len(b) != 0 { + ea, nb, err := parseEa(b) + if err != nil { + return nil, err + } + + eas = append(eas, ea) + b = nb + } + return +} + +func writeEa(buf *bytes.Buffer, ea *ExtendedAttribute, last bool) error { + if int(uint8(len(ea.Name))) != len(ea.Name) { + return errEaNameTooLarge + } + if int(uint16(len(ea.Value))) != len(ea.Value) { + return errEaValueTooLarge + } + entrySize := uint32(fileFullEaInformationSize + len(ea.Name) + 1 + len(ea.Value)) + withPadding := (entrySize + 3) &^ 3 + nextOffset := uint32(0) + if !last { + nextOffset = withPadding + } + info := fileFullEaInformation{ + NextEntryOffset: nextOffset, + Flags: ea.Flags, + NameLength: uint8(len(ea.Name)), + ValueLength: uint16(len(ea.Value)), + } + + err := binary.Write(buf, binary.LittleEndian, &info) + if err != nil { + return err + } + + _, err = buf.Write([]byte(ea.Name)) + if err != nil { + return err + } + + err = buf.WriteByte(0) + if err != nil { + return err + } + + _, err = buf.Write(ea.Value) + if err != nil { + return err + } + + _, err = buf.Write([]byte{0, 0, 0}[0 : withPadding-entrySize]) + if err != nil { + return err + } + + return nil +} + +// EncodeExtendedAttributes encodes a list of EAs into a FILE_FULL_EA_INFORMATION +// buffer for use with BackupWrite, ZwSetEaFile, etc. +func EncodeExtendedAttributes(eas []ExtendedAttribute) ([]byte, error) { + var buf bytes.Buffer + for i := range eas { + last := false + if i == len(eas)-1 { + last = true + } + + err := writeEa(&buf, &eas[i], last) + if err != nil { + return nil, err + } + } + return buf.Bytes(), nil +} diff --git a/vendor/github.com/Microsoft/go-winio/file.go b/vendor/github.com/Microsoft/go-winio/file.go index 613f31b520ee..0385e4108129 100644 --- a/vendor/github.com/Microsoft/go-winio/file.go +++ b/vendor/github.com/Microsoft/go-winio/file.go @@ -16,13 +16,20 @@ import ( //sys createIoCompletionPort(file syscall.Handle, port syscall.Handle, key uintptr, threadCount uint32) (newport syscall.Handle, err error) = CreateIoCompletionPort //sys getQueuedCompletionStatus(port syscall.Handle, bytes *uint32, key *uintptr, o **ioOperation, timeout uint32) (err error) = GetQueuedCompletionStatus //sys setFileCompletionNotificationModes(h syscall.Handle, flags uint8) (err error) = SetFileCompletionNotificationModes -//sys timeBeginPeriod(period uint32) (n int32) = winmm.timeBeginPeriod +//sys wsaGetOverlappedResult(h syscall.Handle, o *syscall.Overlapped, bytes *uint32, wait bool, flags *uint32) (err error) = ws2_32.WSAGetOverlappedResult type atomicBool int32 func (b *atomicBool) isSet() bool { return atomic.LoadInt32((*int32)(b)) != 0 } func (b *atomicBool) setFalse() { atomic.StoreInt32((*int32)(b), 0) } func (b *atomicBool) setTrue() { atomic.StoreInt32((*int32)(b), 1) } +func (b *atomicBool) swap(new bool) bool { + var newInt int32 + if new { + newInt = 1 + } + return atomic.SwapInt32((*int32)(b), newInt) == 1 +} const ( cFILE_SKIP_COMPLETION_PORT_ON_SUCCESS = 1 @@ -71,7 +78,9 @@ func initIo() { type win32File struct { handle syscall.Handle wg sync.WaitGroup - closing bool + wgLock sync.RWMutex + closing atomicBool + socket bool readDeadline deadlineHandler writeDeadline deadlineHandler } @@ -102,19 +111,29 @@ func makeWin32File(h syscall.Handle) (*win32File, error) { } func MakeOpenFile(h syscall.Handle) (io.ReadWriteCloser, error) { - return makeWin32File(h) + // If we return the result of makeWin32File directly, it can result in an + // interface-wrapped nil, rather than a nil interface value. + f, err := makeWin32File(h) + if err != nil { + return nil, err + } + return f, nil } // closeHandle closes the resources associated with a Win32 handle func (f *win32File) closeHandle() { - if !f.closing { + f.wgLock.Lock() + // Atomically set that we are closing, releasing the resources only once. + if !f.closing.swap(true) { + f.wgLock.Unlock() // cancel all IO and wait for it to complete - f.closing = true cancelIoEx(f.handle, nil) f.wg.Wait() // at this point, no new IO can start syscall.Close(f.handle) f.handle = 0 + } else { + f.wgLock.Unlock() } } @@ -127,10 +146,13 @@ func (f *win32File) Close() error { // prepareIo prepares for a new IO operation. // The caller must call f.wg.Done() when the IO is finished, prior to Close() returning. func (f *win32File) prepareIo() (*ioOperation, error) { - f.wg.Add(1) - if f.closing { + f.wgLock.RLock() + if f.closing.isSet() { + f.wgLock.RUnlock() return nil, ErrFileClosed } + f.wg.Add(1) + f.wgLock.RUnlock() c := &ioOperation{} c.ch = make(chan ioResult) return c, nil @@ -138,8 +160,6 @@ func (f *win32File) prepareIo() (*ioOperation, error) { // ioCompletionProcessor processes completed async IOs forever func ioCompletionProcessor(h syscall.Handle) { - // Set the timer resolution to 1. This fixes a performance regression in golang 1.6. - timeBeginPeriod(1) for { var bytes uint32 var key uintptr @@ -159,7 +179,7 @@ func (f *win32File) asyncIo(c *ioOperation, d *deadlineHandler, bytes uint32, er return int(bytes), err } - if f.closing { + if f.closing.isSet() { cancelIoEx(f.handle, &c.o) } @@ -175,9 +195,13 @@ func (f *win32File) asyncIo(c *ioOperation, d *deadlineHandler, bytes uint32, er case r = <-c.ch: err = r.err if err == syscall.ERROR_OPERATION_ABORTED { - if f.closing { + if f.closing.isSet() { err = ErrFileClosed } + } else if err != nil && f.socket { + // err is from Win32. Query the overlapped structure to get the winsock error. + var bytes, flags uint32 + err = wsaGetOverlappedResult(f.handle, &c.o, &bytes, false, &flags) } case <-timeout: cancelIoEx(f.handle, &c.o) @@ -253,6 +277,10 @@ func (f *win32File) Flush() error { return syscall.FlushFileBuffers(f.handle) } +func (f *win32File) Fd() uintptr { + return uintptr(f.handle) +} + func (d *deadlineHandler) set(deadline time.Time) error { d.setLock.Lock() defer d.setLock.Unlock() diff --git a/vendor/github.com/Microsoft/go-winio/fileinfo.go b/vendor/github.com/Microsoft/go-winio/fileinfo.go index b1d60abb8362..ada2fbab6328 100644 --- a/vendor/github.com/Microsoft/go-winio/fileinfo.go +++ b/vendor/github.com/Microsoft/go-winio/fileinfo.go @@ -20,7 +20,8 @@ const ( // FileBasicInfo contains file access time and file attributes information. type FileBasicInfo struct { CreationTime, LastAccessTime, LastWriteTime, ChangeTime syscall.Filetime - FileAttributes uintptr // includes padding + FileAttributes uint32 + pad uint32 // padding } // GetFileBasicInfo retrieves times and attributes for a file. diff --git a/vendor/github.com/Microsoft/go-winio/go.mod b/vendor/github.com/Microsoft/go-winio/go.mod new file mode 100644 index 000000000000..b3846826b40c --- /dev/null +++ b/vendor/github.com/Microsoft/go-winio/go.mod @@ -0,0 +1,9 @@ +module github.com/Microsoft/go-winio + +go 1.12 + +require ( + github.com/pkg/errors v0.8.1 + github.com/sirupsen/logrus v1.4.1 + golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b +) diff --git a/vendor/github.com/Microsoft/go-winio/go.sum b/vendor/github.com/Microsoft/go-winio/go.sum new file mode 100644 index 000000000000..babb4a70df91 --- /dev/null +++ b/vendor/github.com/Microsoft/go-winio/go.sum @@ -0,0 +1,16 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b h1:ag/x1USPSsqHud38I9BAC88qdNLDHHtQ4mlgQIZPPNA= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/vendor/github.com/Microsoft/go-winio/hvsock.go b/vendor/github.com/Microsoft/go-winio/hvsock.go new file mode 100644 index 000000000000..dbfe790ee004 --- /dev/null +++ b/vendor/github.com/Microsoft/go-winio/hvsock.go @@ -0,0 +1,305 @@ +package winio + +import ( + "fmt" + "io" + "net" + "os" + "syscall" + "time" + "unsafe" + + "github.com/Microsoft/go-winio/pkg/guid" +) + +//sys bind(s syscall.Handle, name unsafe.Pointer, namelen int32) (err error) [failretval==socketError] = ws2_32.bind + +const ( + afHvSock = 34 // AF_HYPERV + + socketError = ^uintptr(0) +) + +// An HvsockAddr is an address for a AF_HYPERV socket. +type HvsockAddr struct { + VMID guid.GUID + ServiceID guid.GUID +} + +type rawHvsockAddr struct { + Family uint16 + _ uint16 + VMID guid.GUID + ServiceID guid.GUID +} + +// Network returns the address's network name, "hvsock". +func (addr *HvsockAddr) Network() string { + return "hvsock" +} + +func (addr *HvsockAddr) String() string { + return fmt.Sprintf("%s:%s", &addr.VMID, &addr.ServiceID) +} + +// VsockServiceID returns an hvsock service ID corresponding to the specified AF_VSOCK port. +func VsockServiceID(port uint32) guid.GUID { + g, _ := guid.FromString("00000000-facb-11e6-bd58-64006a7986d3") + g.Data1 = port + return g +} + +func (addr *HvsockAddr) raw() rawHvsockAddr { + return rawHvsockAddr{ + Family: afHvSock, + VMID: addr.VMID, + ServiceID: addr.ServiceID, + } +} + +func (addr *HvsockAddr) fromRaw(raw *rawHvsockAddr) { + addr.VMID = raw.VMID + addr.ServiceID = raw.ServiceID +} + +// HvsockListener is a socket listener for the AF_HYPERV address family. +type HvsockListener struct { + sock *win32File + addr HvsockAddr +} + +// HvsockConn is a connected socket of the AF_HYPERV address family. +type HvsockConn struct { + sock *win32File + local, remote HvsockAddr +} + +func newHvSocket() (*win32File, error) { + fd, err := syscall.Socket(afHvSock, syscall.SOCK_STREAM, 1) + if err != nil { + return nil, os.NewSyscallError("socket", err) + } + f, err := makeWin32File(fd) + if err != nil { + syscall.Close(fd) + return nil, err + } + f.socket = true + return f, nil +} + +// ListenHvsock listens for connections on the specified hvsock address. +func ListenHvsock(addr *HvsockAddr) (_ *HvsockListener, err error) { + l := &HvsockListener{addr: *addr} + sock, err := newHvSocket() + if err != nil { + return nil, l.opErr("listen", err) + } + sa := addr.raw() + err = bind(sock.handle, unsafe.Pointer(&sa), int32(unsafe.Sizeof(sa))) + if err != nil { + return nil, l.opErr("listen", os.NewSyscallError("socket", err)) + } + err = syscall.Listen(sock.handle, 16) + if err != nil { + return nil, l.opErr("listen", os.NewSyscallError("listen", err)) + } + return &HvsockListener{sock: sock, addr: *addr}, nil +} + +func (l *HvsockListener) opErr(op string, err error) error { + return &net.OpError{Op: op, Net: "hvsock", Addr: &l.addr, Err: err} +} + +// Addr returns the listener's network address. +func (l *HvsockListener) Addr() net.Addr { + return &l.addr +} + +// Accept waits for the next connection and returns it. +func (l *HvsockListener) Accept() (_ net.Conn, err error) { + sock, err := newHvSocket() + if err != nil { + return nil, l.opErr("accept", err) + } + defer func() { + if sock != nil { + sock.Close() + } + }() + c, err := l.sock.prepareIo() + if err != nil { + return nil, l.opErr("accept", err) + } + defer l.sock.wg.Done() + + // AcceptEx, per documentation, requires an extra 16 bytes per address. + const addrlen = uint32(16 + unsafe.Sizeof(rawHvsockAddr{})) + var addrbuf [addrlen * 2]byte + + var bytes uint32 + err = syscall.AcceptEx(l.sock.handle, sock.handle, &addrbuf[0], 0, addrlen, addrlen, &bytes, &c.o) + _, err = l.sock.asyncIo(c, nil, bytes, err) + if err != nil { + return nil, l.opErr("accept", os.NewSyscallError("acceptex", err)) + } + conn := &HvsockConn{ + sock: sock, + } + conn.local.fromRaw((*rawHvsockAddr)(unsafe.Pointer(&addrbuf[0]))) + conn.remote.fromRaw((*rawHvsockAddr)(unsafe.Pointer(&addrbuf[addrlen]))) + sock = nil + return conn, nil +} + +// Close closes the listener, causing any pending Accept calls to fail. +func (l *HvsockListener) Close() error { + return l.sock.Close() +} + +/* Need to finish ConnectEx handling +func DialHvsock(ctx context.Context, addr *HvsockAddr) (*HvsockConn, error) { + sock, err := newHvSocket() + if err != nil { + return nil, err + } + defer func() { + if sock != nil { + sock.Close() + } + }() + c, err := sock.prepareIo() + if err != nil { + return nil, err + } + defer sock.wg.Done() + var bytes uint32 + err = windows.ConnectEx(windows.Handle(sock.handle), sa, nil, 0, &bytes, &c.o) + _, err = sock.asyncIo(ctx, c, nil, bytes, err) + if err != nil { + return nil, err + } + conn := &HvsockConn{ + sock: sock, + remote: *addr, + } + sock = nil + return conn, nil +} +*/ + +func (conn *HvsockConn) opErr(op string, err error) error { + return &net.OpError{Op: op, Net: "hvsock", Source: &conn.local, Addr: &conn.remote, Err: err} +} + +func (conn *HvsockConn) Read(b []byte) (int, error) { + c, err := conn.sock.prepareIo() + if err != nil { + return 0, conn.opErr("read", err) + } + defer conn.sock.wg.Done() + buf := syscall.WSABuf{Buf: &b[0], Len: uint32(len(b))} + var flags, bytes uint32 + err = syscall.WSARecv(conn.sock.handle, &buf, 1, &bytes, &flags, &c.o, nil) + n, err := conn.sock.asyncIo(c, &conn.sock.readDeadline, bytes, err) + if err != nil { + if _, ok := err.(syscall.Errno); ok { + err = os.NewSyscallError("wsarecv", err) + } + return 0, conn.opErr("read", err) + } else if n == 0 { + err = io.EOF + } + return n, err +} + +func (conn *HvsockConn) Write(b []byte) (int, error) { + t := 0 + for len(b) != 0 { + n, err := conn.write(b) + if err != nil { + return t + n, err + } + t += n + b = b[n:] + } + return t, nil +} + +func (conn *HvsockConn) write(b []byte) (int, error) { + c, err := conn.sock.prepareIo() + if err != nil { + return 0, conn.opErr("write", err) + } + defer conn.sock.wg.Done() + buf := syscall.WSABuf{Buf: &b[0], Len: uint32(len(b))} + var bytes uint32 + err = syscall.WSASend(conn.sock.handle, &buf, 1, &bytes, 0, &c.o, nil) + n, err := conn.sock.asyncIo(c, &conn.sock.writeDeadline, bytes, err) + if err != nil { + if _, ok := err.(syscall.Errno); ok { + err = os.NewSyscallError("wsasend", err) + } + return 0, conn.opErr("write", err) + } + return n, err +} + +// Close closes the socket connection, failing any pending read or write calls. +func (conn *HvsockConn) Close() error { + return conn.sock.Close() +} + +func (conn *HvsockConn) shutdown(how int) error { + err := syscall.Shutdown(conn.sock.handle, syscall.SHUT_RD) + if err != nil { + return os.NewSyscallError("shutdown", err) + } + return nil +} + +// CloseRead shuts down the read end of the socket. +func (conn *HvsockConn) CloseRead() error { + err := conn.shutdown(syscall.SHUT_RD) + if err != nil { + return conn.opErr("close", err) + } + return nil +} + +// CloseWrite shuts down the write end of the socket, notifying the other endpoint that +// no more data will be written. +func (conn *HvsockConn) CloseWrite() error { + err := conn.shutdown(syscall.SHUT_WR) + if err != nil { + return conn.opErr("close", err) + } + return nil +} + +// LocalAddr returns the local address of the connection. +func (conn *HvsockConn) LocalAddr() net.Addr { + return &conn.local +} + +// RemoteAddr returns the remote address of the connection. +func (conn *HvsockConn) RemoteAddr() net.Addr { + return &conn.remote +} + +// SetDeadline implements the net.Conn SetDeadline method. +func (conn *HvsockConn) SetDeadline(t time.Time) error { + conn.SetReadDeadline(t) + conn.SetWriteDeadline(t) + return nil +} + +// SetReadDeadline implements the net.Conn SetReadDeadline method. +func (conn *HvsockConn) SetReadDeadline(t time.Time) error { + return conn.sock.SetReadDeadline(t) +} + +// SetWriteDeadline implements the net.Conn SetWriteDeadline method. +func (conn *HvsockConn) SetWriteDeadline(t time.Time) error { + return conn.sock.SetWriteDeadline(t) +} diff --git a/vendor/github.com/Microsoft/go-winio/pipe.go b/vendor/github.com/Microsoft/go-winio/pipe.go index da706cc8a7da..d6a46f6a246f 100644 --- a/vendor/github.com/Microsoft/go-winio/pipe.go +++ b/vendor/github.com/Microsoft/go-winio/pipe.go @@ -3,10 +3,13 @@ package winio import ( + "context" "errors" + "fmt" "io" "net" "os" + "runtime" "syscall" "time" "unsafe" @@ -15,31 +18,72 @@ import ( //sys connectNamedPipe(pipe syscall.Handle, o *syscall.Overlapped) (err error) = ConnectNamedPipe //sys createNamedPipe(name string, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *syscall.SecurityAttributes) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateNamedPipeW //sys createFile(name string, access uint32, mode uint32, sa *syscall.SecurityAttributes, createmode uint32, attrs uint32, templatefile syscall.Handle) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateFileW -//sys waitNamedPipe(name string, timeout uint32) (err error) = WaitNamedPipeW //sys getNamedPipeInfo(pipe syscall.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) = GetNamedPipeInfo //sys getNamedPipeHandleState(pipe syscall.Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) = GetNamedPipeHandleStateW //sys localAlloc(uFlags uint32, length uint32) (ptr uintptr) = LocalAlloc +//sys ntCreateNamedPipeFile(pipe *syscall.Handle, access uint32, oa *objectAttributes, iosb *ioStatusBlock, share uint32, disposition uint32, options uint32, typ uint32, readMode uint32, completionMode uint32, maxInstances uint32, inboundQuota uint32, outputQuota uint32, timeout *int64) (status ntstatus) = ntdll.NtCreateNamedPipeFile +//sys rtlNtStatusToDosError(status ntstatus) (winerr error) = ntdll.RtlNtStatusToDosErrorNoTeb +//sys rtlDosPathNameToNtPathName(name *uint16, ntName *unicodeString, filePart uintptr, reserved uintptr) (status ntstatus) = ntdll.RtlDosPathNameToNtPathName_U +//sys rtlDefaultNpAcl(dacl *uintptr) (status ntstatus) = ntdll.RtlDefaultNpAcl + +type ioStatusBlock struct { + Status, Information uintptr +} + +type objectAttributes struct { + Length uintptr + RootDirectory uintptr + ObjectName *unicodeString + Attributes uintptr + SecurityDescriptor *securityDescriptor + SecurityQoS uintptr +} + +type unicodeString struct { + Length uint16 + MaximumLength uint16 + Buffer uintptr +} + +type securityDescriptor struct { + Revision byte + Sbz1 byte + Control uint16 + Owner uintptr + Group uintptr + Sacl uintptr + Dacl uintptr +} + +type ntstatus int32 + +func (status ntstatus) Err() error { + if status >= 0 { + return nil + } + return rtlNtStatusToDosError(status) +} const ( cERROR_PIPE_BUSY = syscall.Errno(231) + cERROR_NO_DATA = syscall.Errno(232) cERROR_PIPE_CONNECTED = syscall.Errno(535) cERROR_SEM_TIMEOUT = syscall.Errno(121) - cPIPE_ACCESS_DUPLEX = 0x3 - cFILE_FLAG_FIRST_PIPE_INSTANCE = 0x80000 - cSECURITY_SQOS_PRESENT = 0x100000 - cSECURITY_ANONYMOUS = 0 + cSECURITY_SQOS_PRESENT = 0x100000 + cSECURITY_ANONYMOUS = 0 - cPIPE_REJECT_REMOTE_CLIENTS = 0x8 + cPIPE_TYPE_MESSAGE = 4 - cPIPE_UNLIMITED_INSTANCES = 255 + cPIPE_READMODE_MESSAGE = 2 - cNMPWAIT_USE_DEFAULT_WAIT = 0 - cNMPWAIT_NOWAIT = 1 + cFILE_OPEN = 1 + cFILE_CREATE = 2 - cPIPE_TYPE_MESSAGE = 4 + cFILE_PIPE_MESSAGE_TYPE = 1 + cFILE_PIPE_REJECT_REMOTE_CLIENTS = 2 - cPIPE_READMODE_MESSAGE = 2 + cSE_DACL_PRESENT = 4 ) var ( @@ -120,6 +164,11 @@ func (f *win32MessageBytePipe) Read(b []byte) (int, error) { // zero-byte message, ensure that all future Read() calls // also return EOF. f.readEOF = true + } else if err == syscall.ERROR_MORE_DATA { + // ERROR_MORE_DATA indicates that the pipe's read mode is message mode + // and the message still has more bytes. Treat this as a success, since + // this package presents all named pipes as byte streams. + err = nil } return n, err } @@ -132,40 +181,53 @@ func (s pipeAddress) String() string { return string(s) } +// tryDialPipe attempts to dial the pipe at `path` until `ctx` cancellation or timeout. +func tryDialPipe(ctx context.Context, path *string) (syscall.Handle, error) { + for { + select { + case <-ctx.Done(): + return syscall.Handle(0), ctx.Err() + default: + h, err := createFile(*path, syscall.GENERIC_READ|syscall.GENERIC_WRITE, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_OVERLAPPED|cSECURITY_SQOS_PRESENT|cSECURITY_ANONYMOUS, 0) + if err == nil { + return h, nil + } + if err != cERROR_PIPE_BUSY { + return h, &os.PathError{Err: err, Op: "open", Path: *path} + } + // Wait 10 msec and try again. This is a rather simplistic + // view, as we always try each 10 milliseconds. + time.Sleep(time.Millisecond * 10) + } + } +} + // DialPipe connects to a named pipe by path, timing out if the connection -// takes longer than the specified duration. If timeout is nil, then the timeout -// is the default timeout established by the pipe server. +// takes longer than the specified duration. If timeout is nil, then we use +// a default timeout of 2 seconds. (We do not use WaitNamedPipe.) func DialPipe(path string, timeout *time.Duration) (net.Conn, error) { var absTimeout time.Time if timeout != nil { absTimeout = time.Now().Add(*timeout) + } else { + absTimeout = time.Now().Add(time.Second * 2) } + ctx, _ := context.WithDeadline(context.Background(), absTimeout) + conn, err := DialPipeContext(ctx, path) + if err == context.DeadlineExceeded { + return nil, ErrTimeout + } + return conn, err +} + +// DialPipeContext attempts to connect to a named pipe by `path` until `ctx` +// cancellation or timeout. +func DialPipeContext(ctx context.Context, path string) (net.Conn, error) { var err error var h syscall.Handle - for { - h, err = createFile(path, syscall.GENERIC_READ|syscall.GENERIC_WRITE, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_OVERLAPPED|cSECURITY_SQOS_PRESENT|cSECURITY_ANONYMOUS, 0) - if err != cERROR_PIPE_BUSY { - break - } - now := time.Now() - var ms uint32 - if absTimeout.IsZero() { - ms = cNMPWAIT_USE_DEFAULT_WAIT - } else if now.After(absTimeout) { - ms = cNMPWAIT_NOWAIT - } else { - ms = uint32(absTimeout.Sub(now).Nanoseconds() / 1000 / 1000) - } - err = waitNamedPipe(path, ms) - if err != nil { - if err == cERROR_SEM_TIMEOUT { - return nil, ErrTimeout - } - break - } - } + h, err = tryDialPipe(ctx, &path) if err != nil { - return nil, &os.PathError{Op: "open", Path: path, Err: err} + return nil, err } var flags uint32 @@ -174,16 +236,6 @@ func DialPipe(path string, timeout *time.Duration) (net.Conn, error) { return nil, err } - var state uint32 - err = getNamedPipeHandleState(h, &state, nil, nil, nil, nil, 0) - if err != nil { - return nil, err - } - - if state&cPIPE_READMODE_MESSAGE != 0 { - return nil, &os.PathError{Op: "open", Path: path, Err: errors.New("message readmode pipes not supported")} - } - f, err := makeWin32File(h) if err != nil { syscall.Close(h) @@ -206,43 +258,87 @@ type acceptResponse struct { } type win32PipeListener struct { - firstHandle syscall.Handle - path string - securityDescriptor []byte - config PipeConfig - acceptCh chan (chan acceptResponse) - closeCh chan int - doneCh chan int + firstHandle syscall.Handle + path string + config PipeConfig + acceptCh chan (chan acceptResponse) + closeCh chan int + doneCh chan int } -func makeServerPipeHandle(path string, securityDescriptor []byte, c *PipeConfig, first bool) (syscall.Handle, error) { - var flags uint32 = cPIPE_ACCESS_DUPLEX | syscall.FILE_FLAG_OVERLAPPED +func makeServerPipeHandle(path string, sd []byte, c *PipeConfig, first bool) (syscall.Handle, error) { + path16, err := syscall.UTF16FromString(path) + if err != nil { + return 0, &os.PathError{Op: "open", Path: path, Err: err} + } + + var oa objectAttributes + oa.Length = unsafe.Sizeof(oa) + + var ntPath unicodeString + if err := rtlDosPathNameToNtPathName(&path16[0], &ntPath, 0, 0).Err(); err != nil { + return 0, &os.PathError{Op: "open", Path: path, Err: err} + } + defer localFree(ntPath.Buffer) + oa.ObjectName = &ntPath + + // The security descriptor is only needed for the first pipe. if first { - flags |= cFILE_FLAG_FIRST_PIPE_INSTANCE + if sd != nil { + len := uint32(len(sd)) + sdb := localAlloc(0, len) + defer localFree(sdb) + copy((*[0xffff]byte)(unsafe.Pointer(sdb))[:], sd) + oa.SecurityDescriptor = (*securityDescriptor)(unsafe.Pointer(sdb)) + } else { + // Construct the default named pipe security descriptor. + var dacl uintptr + if err := rtlDefaultNpAcl(&dacl).Err(); err != nil { + return 0, fmt.Errorf("getting default named pipe ACL: %s", err) + } + defer localFree(dacl) + + sdb := &securityDescriptor{ + Revision: 1, + Control: cSE_DACL_PRESENT, + Dacl: dacl, + } + oa.SecurityDescriptor = sdb + } } - var mode uint32 = cPIPE_REJECT_REMOTE_CLIENTS + typ := uint32(cFILE_PIPE_REJECT_REMOTE_CLIENTS) if c.MessageMode { - mode |= cPIPE_TYPE_MESSAGE + typ |= cFILE_PIPE_MESSAGE_TYPE } - sa := &syscall.SecurityAttributes{} - sa.Length = uint32(unsafe.Sizeof(*sa)) - if securityDescriptor != nil { - len := uint32(len(securityDescriptor)) - sa.SecurityDescriptor = localAlloc(0, len) - defer localFree(sa.SecurityDescriptor) - copy((*[0xffff]byte)(unsafe.Pointer(sa.SecurityDescriptor))[:], securityDescriptor) + disposition := uint32(cFILE_OPEN) + access := uint32(syscall.GENERIC_READ | syscall.GENERIC_WRITE | syscall.SYNCHRONIZE) + if first { + disposition = cFILE_CREATE + // By not asking for read or write access, the named pipe file system + // will put this pipe into an initially disconnected state, blocking + // client connections until the next call with first == false. + access = syscall.SYNCHRONIZE } - h, err := createNamedPipe(path, flags, mode, cPIPE_UNLIMITED_INSTANCES, uint32(c.OutputBufferSize), uint32(c.InputBufferSize), 0, sa) + + timeout := int64(-50 * 10000) // 50ms + + var ( + h syscall.Handle + iosb ioStatusBlock + ) + err = ntCreateNamedPipeFile(&h, access, &oa, &iosb, syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE, disposition, 0, typ, 0, 0, 0xffffffff, uint32(c.InputBufferSize), uint32(c.OutputBufferSize), &timeout).Err() if err != nil { return 0, &os.PathError{Op: "open", Path: path, Err: err} } + + runtime.KeepAlive(ntPath) return h, nil } func (l *win32PipeListener) makeServerPipe() (*win32File, error) { - h, err := makeServerPipeHandle(l.path, l.securityDescriptor, &l.config, false) + h, err := makeServerPipeHandle(l.path, nil, &l.config, false) if err != nil { return nil, err } @@ -254,6 +350,36 @@ func (l *win32PipeListener) makeServerPipe() (*win32File, error) { return f, nil } +func (l *win32PipeListener) makeConnectedServerPipe() (*win32File, error) { + p, err := l.makeServerPipe() + if err != nil { + return nil, err + } + + // Wait for the client to connect. + ch := make(chan error) + go func(p *win32File) { + ch <- connectPipe(p) + }(p) + + select { + case err = <-ch: + if err != nil { + p.Close() + p = nil + } + case <-l.closeCh: + // Abort the connect request by closing the handle. + p.Close() + p = nil + err = <-ch + if err == nil || err == ErrFileClosed { + err = ErrPipeListenerClosed + } + } + return p, err +} + func (l *win32PipeListener) listenerRoutine() { closed := false for !closed { @@ -261,31 +387,20 @@ func (l *win32PipeListener) listenerRoutine() { case <-l.closeCh: closed = true case responseCh := <-l.acceptCh: - p, err := l.makeServerPipe() - if err == nil { - // Wait for the client to connect. - ch := make(chan error) - go func() { - ch <- connectPipe(p) - }() - select { - case err = <-ch: - if err != nil { - p.Close() - p = nil - } - case <-l.closeCh: - // Abort the connect request by closing the handle. - p.Close() - p = nil - err = <-ch - if err == nil || err == ErrFileClosed { - err = ErrPipeListenerClosed - } - closed = true + var ( + p *win32File + err error + ) + for { + p, err = l.makeConnectedServerPipe() + // If the connection was immediately closed by the client, try + // again. + if err != cERROR_NO_DATA { + break } } responseCh <- acceptResponse{p, err} + closed = err == ErrPipeListenerClosed } } syscall.Close(l.firstHandle) @@ -334,22 +449,13 @@ func ListenPipe(path string, c *PipeConfig) (net.Listener, error) { if err != nil { return nil, err } - // Immediately open and then close a client handle so that the named pipe is - // created but not currently accepting connections. - h2, err := createFile(path, 0, 0, nil, syscall.OPEN_EXISTING, cSECURITY_SQOS_PRESENT|cSECURITY_ANONYMOUS, 0) - if err != nil { - syscall.Close(h) - return nil, err - } - syscall.Close(h2) l := &win32PipeListener{ - firstHandle: h, - path: path, - securityDescriptor: sd, - config: *c, - acceptCh: make(chan (chan acceptResponse)), - closeCh: make(chan int), - doneCh: make(chan int), + firstHandle: h, + path: path, + config: *c, + acceptCh: make(chan (chan acceptResponse)), + closeCh: make(chan int), + doneCh: make(chan int), } go l.listenerRoutine() return l, nil diff --git a/vendor/github.com/Microsoft/go-winio/pkg/guid/guid.go b/vendor/github.com/Microsoft/go-winio/pkg/guid/guid.go new file mode 100644 index 000000000000..586406577043 --- /dev/null +++ b/vendor/github.com/Microsoft/go-winio/pkg/guid/guid.go @@ -0,0 +1,235 @@ +// Package guid provides a GUID type. The backing structure for a GUID is +// identical to that used by the golang.org/x/sys/windows GUID type. +// There are two main binary encodings used for a GUID, the big-endian encoding, +// and the Windows (mixed-endian) encoding. See here for details: +// https://en.wikipedia.org/wiki/Universally_unique_identifier#Encoding +package guid + +import ( + "crypto/rand" + "crypto/sha1" + "encoding" + "encoding/binary" + "fmt" + "strconv" + + "golang.org/x/sys/windows" +) + +// Variant specifies which GUID variant (or "type") of the GUID. It determines +// how the entirety of the rest of the GUID is interpreted. +type Variant uint8 + +// The variants specified by RFC 4122. +const ( + // VariantUnknown specifies a GUID variant which does not conform to one of + // the variant encodings specified in RFC 4122. + VariantUnknown Variant = iota + VariantNCS + VariantRFC4122 + VariantMicrosoft + VariantFuture +) + +// Version specifies how the bits in the GUID were generated. For instance, a +// version 4 GUID is randomly generated, and a version 5 is generated from the +// hash of an input string. +type Version uint8 + +var _ = (encoding.TextMarshaler)(GUID{}) +var _ = (encoding.TextUnmarshaler)(&GUID{}) + +// GUID represents a GUID/UUID. It has the same structure as +// golang.org/x/sys/windows.GUID so that it can be used with functions expecting +// that type. It is defined as its own type so that stringification and +// marshaling can be supported. The representation matches that used by native +// Windows code. +type GUID windows.GUID + +// NewV4 returns a new version 4 (pseudorandom) GUID, as defined by RFC 4122. +func NewV4() (GUID, error) { + var b [16]byte + if _, err := rand.Read(b[:]); err != nil { + return GUID{}, err + } + + g := FromArray(b) + g.setVersion(4) // Version 4 means randomly generated. + g.setVariant(VariantRFC4122) + + return g, nil +} + +// NewV5 returns a new version 5 (generated from a string via SHA-1 hashing) +// GUID, as defined by RFC 4122. The RFC is unclear on the encoding of the name, +// and the sample code treats it as a series of bytes, so we do the same here. +// +// Some implementations, such as those found on Windows, treat the name as a +// big-endian UTF16 stream of bytes. If that is desired, the string can be +// encoded as such before being passed to this function. +func NewV5(namespace GUID, name []byte) (GUID, error) { + b := sha1.New() + namespaceBytes := namespace.ToArray() + b.Write(namespaceBytes[:]) + b.Write(name) + + a := [16]byte{} + copy(a[:], b.Sum(nil)) + + g := FromArray(a) + g.setVersion(5) // Version 5 means generated from a string. + g.setVariant(VariantRFC4122) + + return g, nil +} + +func fromArray(b [16]byte, order binary.ByteOrder) GUID { + var g GUID + g.Data1 = order.Uint32(b[0:4]) + g.Data2 = order.Uint16(b[4:6]) + g.Data3 = order.Uint16(b[6:8]) + copy(g.Data4[:], b[8:16]) + return g +} + +func (g GUID) toArray(order binary.ByteOrder) [16]byte { + b := [16]byte{} + order.PutUint32(b[0:4], g.Data1) + order.PutUint16(b[4:6], g.Data2) + order.PutUint16(b[6:8], g.Data3) + copy(b[8:16], g.Data4[:]) + return b +} + +// FromArray constructs a GUID from a big-endian encoding array of 16 bytes. +func FromArray(b [16]byte) GUID { + return fromArray(b, binary.BigEndian) +} + +// ToArray returns an array of 16 bytes representing the GUID in big-endian +// encoding. +func (g GUID) ToArray() [16]byte { + return g.toArray(binary.BigEndian) +} + +// FromWindowsArray constructs a GUID from a Windows encoding array of bytes. +func FromWindowsArray(b [16]byte) GUID { + return fromArray(b, binary.LittleEndian) +} + +// ToWindowsArray returns an array of 16 bytes representing the GUID in Windows +// encoding. +func (g GUID) ToWindowsArray() [16]byte { + return g.toArray(binary.LittleEndian) +} + +func (g GUID) String() string { + return fmt.Sprintf( + "%08x-%04x-%04x-%04x-%012x", + g.Data1, + g.Data2, + g.Data3, + g.Data4[:2], + g.Data4[2:]) +} + +// FromString parses a string containing a GUID and returns the GUID. The only +// format currently supported is the `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` +// format. +func FromString(s string) (GUID, error) { + if len(s) != 36 { + return GUID{}, fmt.Errorf("invalid GUID %q", s) + } + if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' { + return GUID{}, fmt.Errorf("invalid GUID %q", s) + } + + var g GUID + + data1, err := strconv.ParseUint(s[0:8], 16, 32) + if err != nil { + return GUID{}, fmt.Errorf("invalid GUID %q", s) + } + g.Data1 = uint32(data1) + + data2, err := strconv.ParseUint(s[9:13], 16, 16) + if err != nil { + return GUID{}, fmt.Errorf("invalid GUID %q", s) + } + g.Data2 = uint16(data2) + + data3, err := strconv.ParseUint(s[14:18], 16, 16) + if err != nil { + return GUID{}, fmt.Errorf("invalid GUID %q", s) + } + g.Data3 = uint16(data3) + + for i, x := range []int{19, 21, 24, 26, 28, 30, 32, 34} { + v, err := strconv.ParseUint(s[x:x+2], 16, 8) + if err != nil { + return GUID{}, fmt.Errorf("invalid GUID %q", s) + } + g.Data4[i] = uint8(v) + } + + return g, nil +} + +func (g *GUID) setVariant(v Variant) { + d := g.Data4[0] + switch v { + case VariantNCS: + d = (d & 0x7f) + case VariantRFC4122: + d = (d & 0x3f) | 0x80 + case VariantMicrosoft: + d = (d & 0x1f) | 0xc0 + case VariantFuture: + d = (d & 0x0f) | 0xe0 + case VariantUnknown: + fallthrough + default: + panic(fmt.Sprintf("invalid variant: %d", v)) + } + g.Data4[0] = d +} + +// Variant returns the GUID variant, as defined in RFC 4122. +func (g GUID) Variant() Variant { + b := g.Data4[0] + if b&0x80 == 0 { + return VariantNCS + } else if b&0xc0 == 0x80 { + return VariantRFC4122 + } else if b&0xe0 == 0xc0 { + return VariantMicrosoft + } else if b&0xe0 == 0xe0 { + return VariantFuture + } + return VariantUnknown +} + +func (g *GUID) setVersion(v Version) { + g.Data3 = (g.Data3 & 0x0fff) | (uint16(v) << 12) +} + +// Version returns the GUID version, as defined in RFC 4122. +func (g GUID) Version() Version { + return Version((g.Data3 & 0xF000) >> 12) +} + +// MarshalText returns the textual representation of the GUID. +func (g GUID) MarshalText() ([]byte, error) { + return []byte(g.String()), nil +} + +// UnmarshalText takes the textual representation of a GUID, and unmarhals it +// into this GUID. +func (g *GUID) UnmarshalText(text []byte) error { + g2, err := FromString(string(text)) + if err != nil { + return err + } + *g = g2 + return nil +} diff --git a/vendor/github.com/Microsoft/go-winio/syscall.go b/vendor/github.com/Microsoft/go-winio/syscall.go index 20d64cf41d0e..5cb52bc74625 100644 --- a/vendor/github.com/Microsoft/go-winio/syscall.go +++ b/vendor/github.com/Microsoft/go-winio/syscall.go @@ -1,3 +1,3 @@ package winio -//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go file.go pipe.go sd.go fileinfo.go privilege.go backup.go +//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go file.go pipe.go sd.go fileinfo.go privilege.go backup.go hvsock.go diff --git a/vendor/github.com/Microsoft/go-winio/zsyscall_windows.go b/vendor/github.com/Microsoft/go-winio/zsyscall_windows.go index 4f7a52eeb759..e26b01fafb25 100644 --- a/vendor/github.com/Microsoft/go-winio/zsyscall_windows.go +++ b/vendor/github.com/Microsoft/go-winio/zsyscall_windows.go @@ -1,4 +1,4 @@ -// MACHINE GENERATED BY 'go generate' COMMAND; DO NOT EDIT +// Code generated by 'go generate'; DO NOT EDIT. package winio @@ -38,21 +38,25 @@ func errnoErr(e syscall.Errno) error { var ( modkernel32 = windows.NewLazySystemDLL("kernel32.dll") - modwinmm = windows.NewLazySystemDLL("winmm.dll") + modws2_32 = windows.NewLazySystemDLL("ws2_32.dll") + modntdll = windows.NewLazySystemDLL("ntdll.dll") modadvapi32 = windows.NewLazySystemDLL("advapi32.dll") procCancelIoEx = modkernel32.NewProc("CancelIoEx") procCreateIoCompletionPort = modkernel32.NewProc("CreateIoCompletionPort") procGetQueuedCompletionStatus = modkernel32.NewProc("GetQueuedCompletionStatus") procSetFileCompletionNotificationModes = modkernel32.NewProc("SetFileCompletionNotificationModes") - proctimeBeginPeriod = modwinmm.NewProc("timeBeginPeriod") + procWSAGetOverlappedResult = modws2_32.NewProc("WSAGetOverlappedResult") procConnectNamedPipe = modkernel32.NewProc("ConnectNamedPipe") procCreateNamedPipeW = modkernel32.NewProc("CreateNamedPipeW") procCreateFileW = modkernel32.NewProc("CreateFileW") - procWaitNamedPipeW = modkernel32.NewProc("WaitNamedPipeW") procGetNamedPipeInfo = modkernel32.NewProc("GetNamedPipeInfo") procGetNamedPipeHandleStateW = modkernel32.NewProc("GetNamedPipeHandleStateW") procLocalAlloc = modkernel32.NewProc("LocalAlloc") + procNtCreateNamedPipeFile = modntdll.NewProc("NtCreateNamedPipeFile") + procRtlNtStatusToDosErrorNoTeb = modntdll.NewProc("RtlNtStatusToDosErrorNoTeb") + procRtlDosPathNameToNtPathName_U = modntdll.NewProc("RtlDosPathNameToNtPathName_U") + procRtlDefaultNpAcl = modntdll.NewProc("RtlDefaultNpAcl") procLookupAccountNameW = modadvapi32.NewProc("LookupAccountNameW") procConvertSidToStringSidW = modadvapi32.NewProc("ConvertSidToStringSidW") procConvertStringSecurityDescriptorToSecurityDescriptorW = modadvapi32.NewProc("ConvertStringSecurityDescriptorToSecurityDescriptorW") @@ -71,6 +75,7 @@ var ( procLookupPrivilegeDisplayNameW = modadvapi32.NewProc("LookupPrivilegeDisplayNameW") procBackupRead = modkernel32.NewProc("BackupRead") procBackupWrite = modkernel32.NewProc("BackupWrite") + procbind = modws2_32.NewProc("bind") ) func cancelIoEx(file syscall.Handle, o *syscall.Overlapped) (err error) { @@ -122,9 +127,21 @@ func setFileCompletionNotificationModes(h syscall.Handle, flags uint8) (err erro return } -func timeBeginPeriod(period uint32) (n int32) { - r0, _, _ := syscall.Syscall(proctimeBeginPeriod.Addr(), 1, uintptr(period), 0, 0) - n = int32(r0) +func wsaGetOverlappedResult(h syscall.Handle, o *syscall.Overlapped, bytes *uint32, wait bool, flags *uint32) (err error) { + var _p0 uint32 + if wait { + _p0 = 1 + } else { + _p0 = 0 + } + r1, _, e1 := syscall.Syscall6(procWSAGetOverlappedResult.Addr(), 5, uintptr(h), uintptr(unsafe.Pointer(o)), uintptr(unsafe.Pointer(bytes)), uintptr(_p0), uintptr(unsafe.Pointer(flags)), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } return } @@ -184,27 +201,6 @@ func _createFile(name *uint16, access uint32, mode uint32, sa *syscall.SecurityA return } -func waitNamedPipe(name string, timeout uint32) (err error) { - var _p0 *uint16 - _p0, err = syscall.UTF16PtrFromString(name) - if err != nil { - return - } - return _waitNamedPipe(_p0, timeout) -} - -func _waitNamedPipe(name *uint16, timeout uint32) (err error) { - r1, _, e1 := syscall.Syscall(procWaitNamedPipeW.Addr(), 2, uintptr(unsafe.Pointer(name)), uintptr(timeout), 0) - if r1 == 0 { - if e1 != 0 { - err = errnoErr(e1) - } else { - err = syscall.EINVAL - } - } - return -} - func getNamedPipeInfo(pipe syscall.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) { r1, _, e1 := syscall.Syscall6(procGetNamedPipeInfo.Addr(), 5, uintptr(pipe), uintptr(unsafe.Pointer(flags)), uintptr(unsafe.Pointer(outSize)), uintptr(unsafe.Pointer(inSize)), uintptr(unsafe.Pointer(maxInstances)), 0) if r1 == 0 { @@ -235,6 +231,32 @@ func localAlloc(uFlags uint32, length uint32) (ptr uintptr) { return } +func ntCreateNamedPipeFile(pipe *syscall.Handle, access uint32, oa *objectAttributes, iosb *ioStatusBlock, share uint32, disposition uint32, options uint32, typ uint32, readMode uint32, completionMode uint32, maxInstances uint32, inboundQuota uint32, outputQuota uint32, timeout *int64) (status ntstatus) { + r0, _, _ := syscall.Syscall15(procNtCreateNamedPipeFile.Addr(), 14, uintptr(unsafe.Pointer(pipe)), uintptr(access), uintptr(unsafe.Pointer(oa)), uintptr(unsafe.Pointer(iosb)), uintptr(share), uintptr(disposition), uintptr(options), uintptr(typ), uintptr(readMode), uintptr(completionMode), uintptr(maxInstances), uintptr(inboundQuota), uintptr(outputQuota), uintptr(unsafe.Pointer(timeout)), 0) + status = ntstatus(r0) + return +} + +func rtlNtStatusToDosError(status ntstatus) (winerr error) { + r0, _, _ := syscall.Syscall(procRtlNtStatusToDosErrorNoTeb.Addr(), 1, uintptr(status), 0, 0) + if r0 != 0 { + winerr = syscall.Errno(r0) + } + return +} + +func rtlDosPathNameToNtPathName(name *uint16, ntName *unicodeString, filePart uintptr, reserved uintptr) (status ntstatus) { + r0, _, _ := syscall.Syscall6(procRtlDosPathNameToNtPathName_U.Addr(), 4, uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(ntName)), uintptr(filePart), uintptr(reserved), 0, 0) + status = ntstatus(r0) + return +} + +func rtlDefaultNpAcl(dacl *uintptr) (status ntstatus) { + r0, _, _ := syscall.Syscall(procRtlDefaultNpAcl.Addr(), 1, uintptr(unsafe.Pointer(dacl)), 0, 0) + status = ntstatus(r0) + return +} + func lookupAccountName(systemName *uint16, accountName string, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) { var _p0 *uint16 _p0, err = syscall.UTF16PtrFromString(accountName) @@ -526,3 +548,15 @@ func backupWrite(h syscall.Handle, b []byte, bytesWritten *uint32, abort bool, p } return } + +func bind(s syscall.Handle, name unsafe.Pointer, namelen int32) (err error) { + r1, _, e1 := syscall.Syscall(procbind.Addr(), 3, uintptr(s), uintptr(name), uintptr(namelen)) + if r1 == socketError { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} diff --git a/vendor/vendor.json b/vendor/vendor.json index f70b1ec4eee0..19289d4d0042 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -163,10 +163,12 @@ "revisionTime": "2016-07-06T22:07:25Z" }, { - "checksumSHA1": "AzjRkOQtVBTwIw4RJLTygFhJs3s=", + "checksumSHA1": "9vIkKNaaSRELM/2nCkWOAdHD21M=", "path": "github.com/Microsoft/go-winio", - "revision": "f533f7a102197536779ea3a8cb881d639e21ec5a", - "revisionTime": "2017-05-24T00:36:31Z" + "revision": "6c72808b55902eae4c5943626030429ff20f3b63", + "revisionTime": "2019-08-06T19:59:04Z", + "version": "v0.4.14", + "versionExact": "v0.4.14" }, { "checksumSHA1": "7GaOyxvD4mLFr39pWg40CGZ0yiM=", @@ -2060,12 +2062,6 @@ "revision": "65acae22fc9d1fe290b33faa2bd64cdc20a463a0", "revisionTime": "2019-07-23T19:02:41Z" }, - { - "checksumSHA1": "rm+73svMGdyHzoOolT6CGQ9+rkg=", - "path": "github.com/golang/protobuf/jsonpb", - "revision": "6c65a5562fc06764971b7c5d05c76c75e84bdbf7", - "revisionTime": "2019-07-01T18:22:01Z" - }, { "checksumSHA1": "Humj7F03I9Yl+gUp29qHZK+3vgo=", "path": "github.com/gogo/protobuf/types", @@ -2078,6 +2074,12 @@ "revision": "23def4e6c14b4da8ac2ed8007337bc5eb5007998", "revisionTime": "2016-01-25T20:49:56Z" }, + { + "checksumSHA1": "rm+73svMGdyHzoOolT6CGQ9+rkg=", + "path": "github.com/golang/protobuf/jsonpb", + "revision": "6c65a5562fc06764971b7c5d05c76c75e84bdbf7", + "revisionTime": "2019-07-01T18:22:01Z" + }, { "checksumSHA1": "Y2MOwzNZfl4NRNDbLCZa6sgx7O0=", "path": "github.com/golang/protobuf/proto", @@ -2296,6 +2298,12 @@ "revision": "d520615e531a6bf3fb69406b9eba718261285ec8", "revisionTime": "2016-12-05T14:13:22Z" }, + { + "checksumSHA1": "dF75743hHL364Dx3HKdZbBBFrpE=", + "path": "github.com/grpc-ecosystem/go-grpc-prometheus", + "revision": "ae0d8660c5f2108ca70a3776dbe0fb53cf79f1da", + "revisionTime": "2019-04-02T11:54:22Z" + }, { "checksumSHA1": "F4nSSbZ6FkoRA2j/AT7M7AKLn1Q=", "path": "github.com/grpc-ecosystem/grpc-gateway/internal", @@ -2314,12 +2322,6 @@ "revision": "d63917fcb0d53f39184485b9b6a0893af18a5668", "revisionTime": "2019-07-30T05:26:13Z" }, - { - "checksumSHA1": "dF75743hHL364Dx3HKdZbBBFrpE=", - "path": "github.com/grpc-ecosystem/go-grpc-prometheus", - "revision": "ae0d8660c5f2108ca70a3776dbe0fb53cf79f1da", - "revisionTime": "2019-04-02T11:54:22Z" - }, { "checksumSHA1": "P1Enisj7Nzf3wMl6lgPPar0ljoY=", "path": "github.com/hashicorp/go-immutable-radix", diff --git a/winlogbeat/winlogbeat.reference.yml b/winlogbeat/winlogbeat.reference.yml index 0ea03874354c..ab9e5895e910 100644 --- a/winlogbeat/winlogbeat.reference.yml +++ b/winlogbeat/winlogbeat.reference.yml @@ -991,7 +991,7 @@ setup.template.settings: #setup.ilm.enabled: auto # Set the prefix used in the index lifecycle write alias name. The default alias -# name is 'winlogbeat-%{[agent.version]}'. +# name is 'winlogbeat-%{[agent.version]}'. #setup.ilm.rollover_alias: "winlogbeat" # Set the rollover index pattern. The default is "%{now/d}-000001". @@ -1254,12 +1254,21 @@ logging.files: # Defines if the HTTP endpoint is enabled. #http.enabled: false -# The HTTP endpoint will bind to this hostname or IP address. It is recommended to use only localhost. +# The HTTP endpoint will bind to this hostname, IP address, unix socket or named pipe. +# When using IP addresses, it is recommended to only use localhost. #http.host: localhost # Port on which the HTTP endpoint will bind. Default is 5066. #http.port: 5066 +# Define which user should be owning the named pipe. +#http.named_pipe.user: + +# Define which the permissions that should be applied to the named pipe, use the Security +# Descriptor Definition Language (SDDL) to define the permission. This option cannot be used with +# `http.user`. +#http.named_pipe.security_descriptor: + #============================= Process Security ================================ # Enable or disable seccomp system call filtering on Linux. Default is enabled. diff --git a/x-pack/auditbeat/auditbeat.reference.yml b/x-pack/auditbeat/auditbeat.reference.yml index acbce214c97d..b1d3c75b525f 100644 --- a/x-pack/auditbeat/auditbeat.reference.yml +++ b/x-pack/auditbeat/auditbeat.reference.yml @@ -1121,7 +1121,7 @@ setup.template.settings: #setup.ilm.enabled: auto # Set the prefix used in the index lifecycle write alias name. The default alias -# name is 'auditbeat-%{[agent.version]}'. +# name is 'auditbeat-%{[agent.version]}'. #setup.ilm.rollover_alias: "auditbeat" # Set the rollover index pattern. The default is "%{now/d}-000001". @@ -1384,12 +1384,21 @@ logging.files: # Defines if the HTTP endpoint is enabled. #http.enabled: false -# The HTTP endpoint will bind to this hostname or IP address. It is recommended to use only localhost. +# The HTTP endpoint will bind to this hostname, IP address, unix socket or named pipe. +# When using IP addresses, it is recommended to only use localhost. #http.host: localhost # Port on which the HTTP endpoint will bind. Default is 5066. #http.port: 5066 +# Define which user should be owning the named pipe. +#http.named_pipe.user: + +# Define which the permissions that should be applied to the named pipe, use the Security +# Descriptor Definition Language (SDDL) to define the permission. This option cannot be used with +# `http.user`. +#http.named_pipe.security_descriptor: + #============================= Process Security ================================ # Enable or disable seccomp system call filtering on Linux. Default is enabled. diff --git a/x-pack/filebeat/filebeat.reference.yml b/x-pack/filebeat/filebeat.reference.yml index 979d7b669725..7a09bcefb6bc 100644 --- a/x-pack/filebeat/filebeat.reference.yml +++ b/x-pack/filebeat/filebeat.reference.yml @@ -2083,7 +2083,7 @@ setup.template.settings: #setup.ilm.enabled: auto # Set the prefix used in the index lifecycle write alias name. The default alias -# name is 'filebeat-%{[agent.version]}'. +# name is 'filebeat-%{[agent.version]}'. #setup.ilm.rollover_alias: "filebeat" # Set the rollover index pattern. The default is "%{now/d}-000001". @@ -2346,12 +2346,21 @@ logging.files: # Defines if the HTTP endpoint is enabled. #http.enabled: false -# The HTTP endpoint will bind to this hostname or IP address. It is recommended to use only localhost. +# The HTTP endpoint will bind to this hostname, IP address, unix socket or named pipe. +# When using IP addresses, it is recommended to only use localhost. #http.host: localhost # Port on which the HTTP endpoint will bind. Default is 5066. #http.port: 5066 +# Define which user should be owning the named pipe. +#http.named_pipe.user: + +# Define which the permissions that should be applied to the named pipe, use the Security +# Descriptor Definition Language (SDDL) to define the permission. This option cannot be used with +# `http.user`. +#http.named_pipe.security_descriptor: + #============================= Process Security ================================ # Enable or disable seccomp system call filtering on Linux. Default is enabled. diff --git a/x-pack/functionbeat/functionbeat.reference.yml b/x-pack/functionbeat/functionbeat.reference.yml index 3e55f4ace5c3..5b3bc9588155 100644 --- a/x-pack/functionbeat/functionbeat.reference.yml +++ b/x-pack/functionbeat/functionbeat.reference.yml @@ -912,7 +912,7 @@ setup.template.settings: #setup.ilm.enabled: auto # Set the prefix used in the index lifecycle write alias name. The default alias -# name is 'functionbeat-%{[agent.version]}'. +# name is 'functionbeat-%{[agent.version]}'. #setup.ilm.rollover_alias: "functionbeat" # Set the rollover index pattern. The default is "%{now/d}-000001". @@ -1175,12 +1175,21 @@ logging.files: # Defines if the HTTP endpoint is enabled. #http.enabled: false -# The HTTP endpoint will bind to this hostname or IP address. It is recommended to use only localhost. +# The HTTP endpoint will bind to this hostname, IP address, unix socket or named pipe. +# When using IP addresses, it is recommended to only use localhost. #http.host: localhost # Port on which the HTTP endpoint will bind. Default is 5066. #http.port: 5066 +# Define which user should be owning the named pipe. +#http.named_pipe.user: + +# Define which the permissions that should be applied to the named pipe, use the Security +# Descriptor Definition Language (SDDL) to define the permission. This option cannot be used with +# `http.user`. +#http.named_pipe.security_descriptor: + #============================= Process Security ================================ # Enable or disable seccomp system call filtering on Linux. Default is enabled. diff --git a/x-pack/metricbeat/metricbeat.reference.yml b/x-pack/metricbeat/metricbeat.reference.yml index e324dd9488ec..95d4628f29b0 100644 --- a/x-pack/metricbeat/metricbeat.reference.yml +++ b/x-pack/metricbeat/metricbeat.reference.yml @@ -1869,7 +1869,7 @@ setup.template.settings: #setup.ilm.enabled: auto # Set the prefix used in the index lifecycle write alias name. The default alias -# name is 'metricbeat-%{[agent.version]}'. +# name is 'metricbeat-%{[agent.version]}'. #setup.ilm.rollover_alias: "metricbeat" # Set the rollover index pattern. The default is "%{now/d}-000001". @@ -2132,12 +2132,21 @@ logging.files: # Defines if the HTTP endpoint is enabled. #http.enabled: false -# The HTTP endpoint will bind to this hostname or IP address. It is recommended to use only localhost. +# The HTTP endpoint will bind to this hostname, IP address, unix socket or named pipe. +# When using IP addresses, it is recommended to only use localhost. #http.host: localhost # Port on which the HTTP endpoint will bind. Default is 5066. #http.port: 5066 +# Define which user should be owning the named pipe. +#http.named_pipe.user: + +# Define which the permissions that should be applied to the named pipe, use the Security +# Descriptor Definition Language (SDDL) to define the permission. This option cannot be used with +# `http.user`. +#http.named_pipe.security_descriptor: + #============================= Process Security ================================ # Enable or disable seccomp system call filtering on Linux. Default is enabled. diff --git a/x-pack/winlogbeat/winlogbeat.reference.yml b/x-pack/winlogbeat/winlogbeat.reference.yml index 084038dd90fa..01d6e51342c0 100644 --- a/x-pack/winlogbeat/winlogbeat.reference.yml +++ b/x-pack/winlogbeat/winlogbeat.reference.yml @@ -1003,7 +1003,7 @@ setup.template.settings: #setup.ilm.enabled: auto # Set the prefix used in the index lifecycle write alias name. The default alias -# name is 'winlogbeat-%{[agent.version]}'. +# name is 'winlogbeat-%{[agent.version]}'. #setup.ilm.rollover_alias: "winlogbeat" # Set the rollover index pattern. The default is "%{now/d}-000001". @@ -1266,12 +1266,21 @@ logging.files: # Defines if the HTTP endpoint is enabled. #http.enabled: false -# The HTTP endpoint will bind to this hostname or IP address. It is recommended to use only localhost. +# The HTTP endpoint will bind to this hostname, IP address, unix socket or named pipe. +# When using IP addresses, it is recommended to only use localhost. #http.host: localhost # Port on which the HTTP endpoint will bind. Default is 5066. #http.port: 5066 +# Define which user should be owning the named pipe. +#http.named_pipe.user: + +# Define which the permissions that should be applied to the named pipe, use the Security +# Descriptor Definition Language (SDDL) to define the permission. This option cannot be used with +# `http.user`. +#http.named_pipe.security_descriptor: + #============================= Process Security ================================ # Enable or disable seccomp system call filtering on Linux. Default is enabled.