Skip to content

Commit 9d07e6f

Browse files
authored
Move api and api/npipe and monitoring (#17)
1 parent ddc9c44 commit 9d07e6f

39 files changed

+3914
-7
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
This repository is the home to the common libraries used by Elastic Agent and Beats.
44

55
Provided packages:
6+
* `github.com/elastic/elastic-agent-libs/api` Provides an HTTP API for debugging information.
7+
* `github.com/elastic/elastic-agent-libs/api/npipe` Provides an API for debugging information via named pipes.
8+
* `github.com/elastic/elastic-agent-libs/monitoring` Basic monitoring functionality used by Beats and Agent.
69
* `github.com/elastic/elastic-agent-libs/atomic` Atomic operations for integer and boolean types.
710
* `github.com/elastic/elastic-agent-libs/cloudid` is used for parsing `cloud.id` and `cloud.auth` when connecting to the Elastic stack.
811
* `github.com/elastic/elastic-agent-libs/config` the previous `config.go` file from `github.com/elastic/beats/v7/libbeat/common`. A minimal wrapper around `github.com/elastic/go-ucfg`. It contains helpers for merging and accessing configuration objects and flags.

api/config.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Licensed to Elasticsearch B.V. under one or more contributor
2+
// license agreements. See the NOTICE file distributed with
3+
// this work for additional information regarding copyright
4+
// ownership. Elasticsearch B.V. licenses this file to you under
5+
// the Apache License, Version 2.0 (the "License"); you may
6+
// not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package api
19+
20+
import "os"
21+
22+
// Config is the configuration for the API endpoint.
23+
type Config struct {
24+
Enabled bool `config:"enabled"`
25+
Host string `config:"host"`
26+
Port int `config:"port"`
27+
User string `config:"named_pipe.user"`
28+
SecurityDescriptor string `config:"named_pipe.security_descriptor"`
29+
}
30+
31+
// DefaultConfig is the default configuration used by the API endpoint.
32+
func DefaultConfig() Config {
33+
return Config{
34+
Enabled: false,
35+
Host: "localhost",
36+
Port: 5066,
37+
}
38+
}
39+
40+
// File mode for the socket file, owner of the process can do everything, member of the group can read.
41+
const socketFileMode = os.FileMode(0740)

api/make_listener_posix.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Licensed to Elasticsearch B.V. under one or more contributor
2+
// license agreements. See the NOTICE file distributed with
3+
// this work for additional information regarding copyright
4+
// ownership. Elasticsearch B.V. licenses this file to you under
5+
// the Apache License, Version 2.0 (the "License"); you may
6+
// not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
//go:build !windows
19+
// +build !windows
20+
21+
package api
22+
23+
import (
24+
"errors"
25+
"fmt"
26+
"net"
27+
"os"
28+
29+
"github.com/elastic/elastic-agent-libs/api/npipe"
30+
)
31+
32+
func makeListener(cfg Config) (net.Listener, error) {
33+
if len(cfg.User) > 0 {
34+
return nil, errors.New("specifying a user is not supported under this platform")
35+
}
36+
37+
if len(cfg.SecurityDescriptor) > 0 {
38+
return nil, errors.New("security_descriptor option for the HTTP endpoint only work on Windows")
39+
}
40+
41+
if npipe.IsNPipe(cfg.Host) {
42+
return nil, fmt.Errorf("cannot use %s as the host, named pipes are only supported on Windows", cfg.Host)
43+
}
44+
45+
network, path, err := parse(cfg.Host, cfg.Port)
46+
if err != nil {
47+
return nil, err
48+
}
49+
50+
if network == unixNetwork {
51+
if _, err := os.Stat(path); !os.IsNotExist(err) {
52+
if err := os.Remove(path); err != nil {
53+
return nil, fmt.Errorf("cannot remove existing unix socket file at location %s: %w", path, err)
54+
}
55+
}
56+
}
57+
58+
l, err := net.Listen(network, path)
59+
if err != nil {
60+
return nil, err
61+
}
62+
63+
// Ensure file mode
64+
if network == unixNetwork {
65+
if err := os.Chmod(path, socketFileMode); err != nil {
66+
return nil, fmt.Errorf("could not set mode %d for unix socket file at location %s: %w",
67+
socketFileMode,
68+
path,
69+
err,
70+
)
71+
}
72+
}
73+
74+
return l, nil
75+
}

api/make_listener_windows.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Licensed to Elasticsearch B.V. under one or more contributor
2+
// license agreements. See the NOTICE file distributed with
3+
// this work for additional information regarding copyright
4+
// ownership. Elasticsearch B.V. licenses this file to you under
5+
// the Apache License, Version 2.0 (the "License"); you may
6+
// not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
//go:build windows
19+
// +build windows
20+
21+
package api
22+
23+
import (
24+
"errors"
25+
"fmt"
26+
"net"
27+
28+
"github.com/elastic/elastic-agent-libs/api/npipe"
29+
)
30+
31+
func makeListener(cfg Config) (net.Listener, error) {
32+
if len(cfg.User) > 0 && len(cfg.SecurityDescriptor) > 0 {
33+
return nil, errors.New("user and security_descriptor are mutually exclusive, define only one of them")
34+
}
35+
36+
if npipe.IsNPipe(cfg.Host) {
37+
pipe := npipe.TransformString(cfg.Host)
38+
var sd string
39+
var err error
40+
if len(cfg.SecurityDescriptor) == 0 {
41+
sd, err = npipe.DefaultSD(cfg.User)
42+
if err != nil {
43+
return nil, fmt.Errorf("cannot generate security descriptor for the named pipe: %w", err)
44+
}
45+
} else {
46+
sd = cfg.SecurityDescriptor
47+
}
48+
return npipe.NewListener(pipe, sd)
49+
}
50+
51+
network, path, err := parse(cfg.Host, cfg.Port)
52+
if err != nil {
53+
return nil, err
54+
}
55+
56+
if network == unixNetwork {
57+
return nil, fmt.Errorf(
58+
"cannot use %s as the host, unix sockets are not supported on Windows, use npipe instead",
59+
cfg.Host,
60+
)
61+
}
62+
63+
return net.Listen(network, path)
64+
}

api/npipe/listener_windows.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// Licensed to Elasticsearch B.V. under one or more contributor
2+
// license agreements. See the NOTICE file distributed with
3+
// this work for additional information regarding copyright
4+
// ownership. Elasticsearch B.V. licenses this file to you under
5+
// the Apache License, Version 2.0 (the "License"); you may
6+
// not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
//go:build windows
19+
// +build windows
20+
21+
package npipe
22+
23+
import (
24+
"context"
25+
"fmt"
26+
"net"
27+
"os/user"
28+
"strings"
29+
30+
winio "github.com/Microsoft/go-winio"
31+
)
32+
33+
// NewListener creates a new Listener receiving events over a named pipe.
34+
func NewListener(name, sd string) (net.Listener, error) {
35+
c := &winio.PipeConfig{
36+
SecurityDescriptor: sd,
37+
}
38+
39+
l, err := winio.ListenPipe(name, c)
40+
if err != nil {
41+
return nil, fmt.Errorf("failed to listen on the named pipe %s: %w", name, err)
42+
}
43+
44+
return l, nil
45+
}
46+
47+
// TransformString takes an input type name defined as a URI like `npipe:///hello` and transform it into
48+
// `\\.\pipe\hello`
49+
func TransformString(name string) string {
50+
if strings.HasPrefix(name, "npipe:///") {
51+
path := strings.TrimPrefix(name, "npipe:///")
52+
return `\\.\pipe\` + path
53+
}
54+
55+
if strings.HasPrefix(name, `\\.\pipe\`) {
56+
return name
57+
}
58+
59+
return name
60+
}
61+
62+
// DialContext create a Dial to be use with an http.Client to connect to a pipe.
63+
func DialContext(npipe string) func(context.Context, string, string) (net.Conn, error) {
64+
return func(ctx context.Context, _, _ string) (net.Conn, error) {
65+
return winio.DialPipeContext(ctx, npipe)
66+
}
67+
}
68+
69+
// Dial create a Dial to be use with an http.Client to connect to a pipe.
70+
func Dial(npipe string) func(string, string) (net.Conn, error) {
71+
return func(_, _ string) (net.Conn, error) {
72+
return winio.DialPipe(npipe, nil)
73+
}
74+
}
75+
76+
// DefaultSD returns a default SecurityDescriptor which is the minimal required permissions to be
77+
// able to write to the named pipe. The security descriptor is returned in SDDL format.
78+
//
79+
// Docs: https://docs.microsoft.com/en-us/windows/win32/secauthz/security-descriptor-string-format
80+
func DefaultSD(forUser string) (string, error) {
81+
var u *user.User
82+
var err error
83+
// No user configured we fallback to the current running user.
84+
if len(forUser) == 0 {
85+
u, err = user.Current()
86+
if err != nil {
87+
return "", fmt.Errorf("failed to retrieve the current user: %w", err)
88+
}
89+
} else {
90+
u, err = user.Lookup(forUser)
91+
if err != nil {
92+
return "", fmt.Errorf("failed to retrieve the user %s: %w", forUser, err)
93+
}
94+
}
95+
96+
// Named pipe security and access rights.
97+
// We create the pipe and the specific users should only be able to write to it.
98+
// See docs: https://docs.microsoft.com/en-us/windows/win32/ipc/named-pipe-security-and-access-rights
99+
// String definition: https://docs.microsoft.com/en-us/windows/win32/secauthz/ace-strings
100+
// Give generic read/write access to the specified user.
101+
descriptor := "D:P(A;;GA;;;" + u.Uid + ")"
102+
if u.Username == "NT AUTHORITY\\SYSTEM" {
103+
// running as SYSTEM, include Administrators group so Administrators can talk over
104+
// the named pipe to the running Elastic Agent system process
105+
// https://support.microsoft.com/en-us/help/243330/well-known-security-identifiers-in-windows-operating-systems
106+
descriptor += "(A;;GA;;;S-1-5-32-544)" // Administrators group
107+
}
108+
return descriptor, nil
109+
}

api/npipe/listener_windows_test.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Licensed to Elasticsearch B.V. under one or more contributor
2+
// license agreements. See the NOTICE file distributed with
3+
// this work for additional information regarding copyright
4+
// ownership. Elasticsearch B.V. licenses this file to you under
5+
// the Apache License, Version 2.0 (the "License"); you may
6+
// not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
//go:build windows
19+
// +build windows
20+
21+
package npipe
22+
23+
import (
24+
"fmt"
25+
"io/ioutil"
26+
"net/http"
27+
"testing"
28+
29+
"github.com/stretchr/testify/assert"
30+
"github.com/stretchr/testify/require"
31+
)
32+
33+
func TestHTTPOverNamedPipe(t *testing.T) {
34+
sd, err := DefaultSD("")
35+
require.NoError(t, err)
36+
npipe := TransformString("npipe:///hello-world")
37+
l, err := NewListener(npipe, sd)
38+
require.NoError(t, err)
39+
defer l.Close()
40+
41+
mux := http.NewServeMux()
42+
mux.HandleFunc("/echo-hello", func(w http.ResponseWriter, r *http.Request) {
43+
fmt.Fprintf(w, "ehlo!")
44+
})
45+
46+
go func() {
47+
err := http.Serve(l, mux)
48+
require.NoError(t, err)
49+
}()
50+
51+
c := http.Client{
52+
Transport: &http.Transport{
53+
DialContext: DialContext(npipe),
54+
},
55+
}
56+
57+
r, err := c.Get("http://npipe/echo-hello")
58+
require.NoError(t, err)
59+
body, err := ioutil.ReadAll(r.Body)
60+
require.NoError(t, err)
61+
defer r.Body.Close()
62+
63+
assert.Equal(t, "ehlo!", string(body))
64+
}
65+
66+
func TestTransformString(t *testing.T) {
67+
t.Run("with npipe:// scheme", func(t *testing.T) {
68+
assert.Equal(t, `\\.\pipe\hello`, TransformString("npipe:///hello"))
69+
})
70+
71+
t.Run("with windows pipe syntax", func(t *testing.T) {
72+
assert.Equal(t, `\\.\pipe\hello`, TransformString(`\\.\pipe\hello`))
73+
})
74+
75+
t.Run("everything else", func(t *testing.T) {
76+
assert.Equal(t, "hello", TransformString("hello"))
77+
})
78+
}

0 commit comments

Comments
 (0)