api/npipe/listener_windows.go (24 lines of code) (raw):

// 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. //go:build windows package npipe import ( "context" "fmt" "net" "strings" "syscall" "github.com/Microsoft/go-winio" ) // ntAuthoritySystemSID is a well-known SID used by the NT AUTHORITY\SYSTEM account. const ntAuthoritySystemSID = "S-1-5-18" // 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, fmt.Errorf("failed to listen on the named pipe %s: %w", name, err) } return l, nil } // TransformString takes an input type name defined as a URI like // `npipe:///hello` and transforms it into // `\\.\pipe\hello` func TransformString(name string) string { if strings.HasPrefix(name, "npipe:///") { path := strings.TrimPrefix(name, "npipe:///") return `\\.\pipe\` + path } 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) } } // Dial create a Dial to be use with an http.Client to connect to a pipe. func Dial(npipe string) func(string, string) (net.Conn, error) { return func(_, _ string) (net.Conn, error) { return winio.DialPipe(npipe, nil) } } // DefaultSD returns a default SecurityDescriptor that specifies 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) { sid, err := lookupSID(forUser) if err != nil { return "", err } // 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;;;" + sid + ")" if sid == ntAuthoritySystemSID { // running as SYSTEM, include Administrators group so Administrators can talk over // the named pipe to the running Elastic Agent system process // https://support.microsoft.com/en-us/help/243330/well-known-security-identifiers-in-windows-operating-systems descriptor += "(A;;GA;;;S-1-5-32-544)" // Administrators group } return descriptor, nil } // lookupSID returns the SID of the specified username. If username is empty the // SID of the current user is returned. func lookupSID(username string) (string, error) { if username == "" { sid, err := currentUserSID() if err != nil { return "", fmt.Errorf("failed to lookup the SID of current user: %w", err) } return sid, nil } sid, _, _, err := syscall.LookupSID("", username) if err != nil { return "", fmt.Errorf("failed to lookup the SID for user %q: %w", username, err) } sidString, err := sid.String() if err != nil { return "", fmt.Errorf("failed to convert the SID for user %q to string: %w", username, err) } return sidString, nil } // currentUserSID returns the SID of the user running the current process. func currentUserSID() (string, error) { t, err := syscall.OpenCurrentProcessToken() if err != nil { return "", err } defer t.Close() u, err := t.GetTokenUser() if err != nil { return "", err } sid, err := u.User.Sid.String() if err != nil { return "", err } return sid, nil }