From b8ba047b35ae5f6c8529b741bd4c79e4b25ac1bd Mon Sep 17 00:00:00 2001 From: Josh Black Date: Mon, 23 Nov 2020 14:24:32 -0800 Subject: [PATCH] Allow Vault Agent to run as a Windows service (#10231) --- changelog/10231.txt | 3 + command/agent.go | 3 + command/agent/winsvc/service.go | 9 + command/agent/winsvc/service_windows.go | 43 ++ go.mod | 1 + vendor/golang.org/x/sys/windows/svc/event.go | 48 +++ vendor/golang.org/x/sys/windows/svc/go12.c | 24 ++ vendor/golang.org/x/sys/windows/svc/go12.go | 11 + vendor/golang.org/x/sys/windows/svc/go13.go | 31 ++ .../golang.org/x/sys/windows/svc/security.go | 59 +++ .../golang.org/x/sys/windows/svc/service.go | 371 ++++++++++++++++++ vendor/golang.org/x/sys/windows/svc/sys_386.s | 69 ++++ .../golang.org/x/sys/windows/svc/sys_amd64.s | 48 +++ vendor/golang.org/x/sys/windows/svc/sys_arm.s | 38 ++ vendor/modules.txt | 1 + website/data/docs-navigation.js | 1 + website/pages/docs/agent/index.mdx | 1 + website/pages/docs/agent/winsvc/index.mdx | 58 +++ 18 files changed, 819 insertions(+) create mode 100644 changelog/10231.txt create mode 100644 command/agent/winsvc/service.go create mode 100644 command/agent/winsvc/service_windows.go create mode 100644 vendor/golang.org/x/sys/windows/svc/event.go create mode 100644 vendor/golang.org/x/sys/windows/svc/go12.c create mode 100644 vendor/golang.org/x/sys/windows/svc/go12.go create mode 100644 vendor/golang.org/x/sys/windows/svc/go13.go create mode 100644 vendor/golang.org/x/sys/windows/svc/security.go create mode 100644 vendor/golang.org/x/sys/windows/svc/service.go create mode 100644 vendor/golang.org/x/sys/windows/svc/sys_386.s create mode 100644 vendor/golang.org/x/sys/windows/svc/sys_amd64.s create mode 100644 vendor/golang.org/x/sys/windows/svc/sys_arm.s create mode 100644 website/pages/docs/agent/winsvc/index.mdx diff --git a/changelog/10231.txt b/changelog/10231.txt new file mode 100644 index 000000000..c0158e432 --- /dev/null +++ b/changelog/10231.txt @@ -0,0 +1,3 @@ +```release-note:improvement +agent: Agent can now run as a Windows service. +``` diff --git a/command/agent.go b/command/agent.go index 36d19ea06..7ee4f2c75 100644 --- a/command/agent.go +++ b/command/agent.go @@ -35,6 +35,7 @@ import ( "github.com/hashicorp/vault/command/agent/sink/file" "github.com/hashicorp/vault/command/agent/sink/inmem" "github.com/hashicorp/vault/command/agent/template" + "github.com/hashicorp/vault/command/agent/winsvc" "github.com/hashicorp/vault/internalshared/gatedwriter" "github.com/hashicorp/vault/sdk/helper/consts" "github.com/hashicorp/vault/sdk/helper/logging" @@ -558,6 +559,8 @@ func (c *AgentCommand) Run(args []string) int { return nil case <-ctx.Done(): return nil + case <-winsvc.ShutdownChannel(): + return nil } } }, func(error) {}) diff --git a/command/agent/winsvc/service.go b/command/agent/winsvc/service.go new file mode 100644 index 000000000..c8d21f5c7 --- /dev/null +++ b/command/agent/winsvc/service.go @@ -0,0 +1,9 @@ +package winsvc + +var chanGraceExit = make(chan int) + +// ShutdownChannel returns a channel that sends a message that a shutdown +// signal has been received for the service. +func ShutdownChannel() <-chan int { + return chanGraceExit +} diff --git a/command/agent/winsvc/service_windows.go b/command/agent/winsvc/service_windows.go new file mode 100644 index 000000000..6e073c84a --- /dev/null +++ b/command/agent/winsvc/service_windows.go @@ -0,0 +1,43 @@ +// +build windows + +package winsvc + +import ( + wsvc "golang.org/x/sys/windows/svc" +) + +type serviceWindows struct{} + +func init() { + interactive, err := wsvc.IsAnInteractiveSession() + if err != nil { + panic(err) + } + // Cannot run as a service when running interactively + if interactive { + return + } + go wsvc.Run("", serviceWindows{}) +} + +// Execute implements the Windows service Handler type. It will be +// called at the start of the service, and the service will exit +// once Execute completes. +func (serviceWindows) Execute(args []string, r <-chan wsvc.ChangeRequest, s chan<- wsvc.Status) (svcSpecificEC bool, exitCode uint32) { + const accCommands = wsvc.AcceptStop | wsvc.AcceptShutdown + s <- wsvc.Status{State: wsvc.StartPending} + s <- wsvc.Status{State: wsvc.Running, Accepts: accCommands} + for { + c := <-r + switch c.Cmd { + case wsvc.Interrogate: + s <- c.CurrentStatus + case wsvc.Stop, wsvc.Shutdown: + s <- wsvc.Status{State: wsvc.StopPending} + chanGraceExit <- 1 + return false, 0 + } + } + + return false, 0 +} diff --git a/go.mod b/go.mod index 0a3862391..723c6ab24 100644 --- a/go.mod +++ b/go.mod @@ -150,6 +150,7 @@ require ( golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 golang.org/x/net v0.0.0-20200625001655-4c5254603344 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d + golang.org/x/sys v0.0.0-20200828194041-157a740278f4 golang.org/x/tools v0.0.0-20200521155704-91d71f6c2f04 google.golang.org/api v0.29.0 google.golang.org/grpc v1.29.1 diff --git a/vendor/golang.org/x/sys/windows/svc/event.go b/vendor/golang.org/x/sys/windows/svc/event.go new file mode 100644 index 000000000..0508e2288 --- /dev/null +++ b/vendor/golang.org/x/sys/windows/svc/event.go @@ -0,0 +1,48 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +package svc + +import ( + "errors" + + "golang.org/x/sys/windows" +) + +// event represents auto-reset, initially non-signaled Windows event. +// It is used to communicate between go and asm parts of this package. +type event struct { + h windows.Handle +} + +func newEvent() (*event, error) { + h, err := windows.CreateEvent(nil, 0, 0, nil) + if err != nil { + return nil, err + } + return &event{h: h}, nil +} + +func (e *event) Close() error { + return windows.CloseHandle(e.h) +} + +func (e *event) Set() error { + return windows.SetEvent(e.h) +} + +func (e *event) Wait() error { + s, err := windows.WaitForSingleObject(e.h, windows.INFINITE) + switch s { + case windows.WAIT_OBJECT_0: + break + case windows.WAIT_FAILED: + return err + default: + return errors.New("unexpected result from WaitForSingleObject") + } + return nil +} diff --git a/vendor/golang.org/x/sys/windows/svc/go12.c b/vendor/golang.org/x/sys/windows/svc/go12.c new file mode 100644 index 000000000..6f1be1fa3 --- /dev/null +++ b/vendor/golang.org/x/sys/windows/svc/go12.c @@ -0,0 +1,24 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows +// +build !go1.3 + +// copied from pkg/runtime +typedef unsigned int uint32; +typedef unsigned long long int uint64; +#ifdef _64BIT +typedef uint64 uintptr; +#else +typedef uint32 uintptr; +#endif + +// from sys_386.s or sys_amd64.s +void ·servicemain(void); + +void +·getServiceMain(uintptr *r) +{ + *r = (uintptr)·servicemain; +} diff --git a/vendor/golang.org/x/sys/windows/svc/go12.go b/vendor/golang.org/x/sys/windows/svc/go12.go new file mode 100644 index 000000000..cd8b913c9 --- /dev/null +++ b/vendor/golang.org/x/sys/windows/svc/go12.go @@ -0,0 +1,11 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows +// +build !go1.3 + +package svc + +// from go12.c +func getServiceMain(r *uintptr) diff --git a/vendor/golang.org/x/sys/windows/svc/go13.go b/vendor/golang.org/x/sys/windows/svc/go13.go new file mode 100644 index 000000000..9d7f3cec5 --- /dev/null +++ b/vendor/golang.org/x/sys/windows/svc/go13.go @@ -0,0 +1,31 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows +// +build go1.3 + +package svc + +import "unsafe" + +const ptrSize = 4 << (^uintptr(0) >> 63) // unsafe.Sizeof(uintptr(0)) but an ideal const + +// Should be a built-in for unsafe.Pointer? +func add(p unsafe.Pointer, x uintptr) unsafe.Pointer { + return unsafe.Pointer(uintptr(p) + x) +} + +// funcPC returns the entry PC of the function f. +// It assumes that f is a func value. Otherwise the behavior is undefined. +func funcPC(f interface{}) uintptr { + return **(**uintptr)(add(unsafe.Pointer(&f), ptrSize)) +} + +// from sys_386.s and sys_amd64.s +func servicectlhandler(ctl uint32) uintptr +func servicemain(argc uint32, argv **uint16) + +func getServiceMain(r *uintptr) { + *r = funcPC(servicemain) +} diff --git a/vendor/golang.org/x/sys/windows/svc/security.go b/vendor/golang.org/x/sys/windows/svc/security.go new file mode 100644 index 000000000..65025998d --- /dev/null +++ b/vendor/golang.org/x/sys/windows/svc/security.go @@ -0,0 +1,59 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +package svc + +import ( + "golang.org/x/sys/windows" +) + +func allocSid(subAuth0 uint32) (*windows.SID, error) { + var sid *windows.SID + err := windows.AllocateAndInitializeSid(&windows.SECURITY_NT_AUTHORITY, + 1, subAuth0, 0, 0, 0, 0, 0, 0, 0, &sid) + if err != nil { + return nil, err + } + return sid, nil +} + +// IsAnInteractiveSession determines if calling process is running interactively. +// It queries the process token for membership in the Interactive group. +// http://stackoverflow.com/questions/2668851/how-do-i-detect-that-my-application-is-running-as-service-or-in-an-interactive-s +func IsAnInteractiveSession() (bool, error) { + interSid, err := allocSid(windows.SECURITY_INTERACTIVE_RID) + if err != nil { + return false, err + } + defer windows.FreeSid(interSid) + + serviceSid, err := allocSid(windows.SECURITY_SERVICE_RID) + if err != nil { + return false, err + } + defer windows.FreeSid(serviceSid) + + t, err := windows.OpenCurrentProcessToken() + if err != nil { + return false, err + } + defer t.Close() + + gs, err := t.GetTokenGroups() + if err != nil { + return false, err + } + + for _, g := range gs.AllGroups() { + if windows.EqualSid(g.Sid, interSid) { + return true, nil + } + if windows.EqualSid(g.Sid, serviceSid) { + return false, nil + } + } + return false, nil +} diff --git a/vendor/golang.org/x/sys/windows/svc/service.go b/vendor/golang.org/x/sys/windows/svc/service.go new file mode 100644 index 000000000..bae818ddd --- /dev/null +++ b/vendor/golang.org/x/sys/windows/svc/service.go @@ -0,0 +1,371 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +// Package svc provides everything required to build Windows service. +// +package svc + +import ( + "errors" + "runtime" + "syscall" + "unsafe" + + "golang.org/x/sys/internal/unsafeheader" + "golang.org/x/sys/windows" +) + +// State describes service execution state (Stopped, Running and so on). +type State uint32 + +const ( + Stopped = State(windows.SERVICE_STOPPED) + StartPending = State(windows.SERVICE_START_PENDING) + StopPending = State(windows.SERVICE_STOP_PENDING) + Running = State(windows.SERVICE_RUNNING) + ContinuePending = State(windows.SERVICE_CONTINUE_PENDING) + PausePending = State(windows.SERVICE_PAUSE_PENDING) + Paused = State(windows.SERVICE_PAUSED) +) + +// Cmd represents service state change request. It is sent to a service +// by the service manager, and should be actioned upon by the service. +type Cmd uint32 + +const ( + Stop = Cmd(windows.SERVICE_CONTROL_STOP) + Pause = Cmd(windows.SERVICE_CONTROL_PAUSE) + Continue = Cmd(windows.SERVICE_CONTROL_CONTINUE) + Interrogate = Cmd(windows.SERVICE_CONTROL_INTERROGATE) + Shutdown = Cmd(windows.SERVICE_CONTROL_SHUTDOWN) + ParamChange = Cmd(windows.SERVICE_CONTROL_PARAMCHANGE) + NetBindAdd = Cmd(windows.SERVICE_CONTROL_NETBINDADD) + NetBindRemove = Cmd(windows.SERVICE_CONTROL_NETBINDREMOVE) + NetBindEnable = Cmd(windows.SERVICE_CONTROL_NETBINDENABLE) + NetBindDisable = Cmd(windows.SERVICE_CONTROL_NETBINDDISABLE) + DeviceEvent = Cmd(windows.SERVICE_CONTROL_DEVICEEVENT) + HardwareProfileChange = Cmd(windows.SERVICE_CONTROL_HARDWAREPROFILECHANGE) + PowerEvent = Cmd(windows.SERVICE_CONTROL_POWEREVENT) + SessionChange = Cmd(windows.SERVICE_CONTROL_SESSIONCHANGE) +) + +// Accepted is used to describe commands accepted by the service. +// Note that Interrogate is always accepted. +type Accepted uint32 + +const ( + AcceptStop = Accepted(windows.SERVICE_ACCEPT_STOP) + AcceptShutdown = Accepted(windows.SERVICE_ACCEPT_SHUTDOWN) + AcceptPauseAndContinue = Accepted(windows.SERVICE_ACCEPT_PAUSE_CONTINUE) + AcceptParamChange = Accepted(windows.SERVICE_ACCEPT_PARAMCHANGE) + AcceptNetBindChange = Accepted(windows.SERVICE_ACCEPT_NETBINDCHANGE) + AcceptHardwareProfileChange = Accepted(windows.SERVICE_ACCEPT_HARDWAREPROFILECHANGE) + AcceptPowerEvent = Accepted(windows.SERVICE_ACCEPT_POWEREVENT) + AcceptSessionChange = Accepted(windows.SERVICE_ACCEPT_SESSIONCHANGE) +) + +// Status combines State and Accepted commands to fully describe running service. +type Status struct { + State State + Accepts Accepted + CheckPoint uint32 // used to report progress during a lengthy operation + WaitHint uint32 // estimated time required for a pending operation, in milliseconds + ProcessId uint32 // if the service is running, the process identifier of it, and otherwise zero +} + +// ChangeRequest is sent to the service Handler to request service status change. +type ChangeRequest struct { + Cmd Cmd + EventType uint32 + EventData uintptr + CurrentStatus Status + Context uintptr +} + +// Handler is the interface that must be implemented to build Windows service. +type Handler interface { + + // Execute will be called by the package code at the start of + // the service, and the service will exit once Execute completes. + // Inside Execute you must read service change requests from r and + // act accordingly. You must keep service control manager up to date + // about state of your service by writing into s as required. + // args contains service name followed by argument strings passed + // to the service. + // You can provide service exit code in exitCode return parameter, + // with 0 being "no error". You can also indicate if exit code, + // if any, is service specific or not by using svcSpecificEC + // parameter. + Execute(args []string, r <-chan ChangeRequest, s chan<- Status) (svcSpecificEC bool, exitCode uint32) +} + +var ( + // These are used by asm code. + goWaitsH uintptr + cWaitsH uintptr + ssHandle uintptr + sName *uint16 + sArgc uintptr + sArgv **uint16 + ctlHandlerExProc uintptr + cSetEvent uintptr + cWaitForSingleObject uintptr + cRegisterServiceCtrlHandlerExW uintptr +) + +func init() { + k := windows.NewLazySystemDLL("kernel32.dll") + cSetEvent = k.NewProc("SetEvent").Addr() + cWaitForSingleObject = k.NewProc("WaitForSingleObject").Addr() + a := windows.NewLazySystemDLL("advapi32.dll") + cRegisterServiceCtrlHandlerExW = a.NewProc("RegisterServiceCtrlHandlerExW").Addr() +} + +type ctlEvent struct { + cmd Cmd + eventType uint32 + eventData uintptr + context uintptr + errno uint32 +} + +// service provides access to windows service api. +type service struct { + name string + h windows.Handle + cWaits *event + goWaits *event + c chan ctlEvent + handler Handler +} + +func newService(name string, handler Handler) (*service, error) { + var s service + var err error + s.name = name + s.c = make(chan ctlEvent) + s.handler = handler + s.cWaits, err = newEvent() + if err != nil { + return nil, err + } + s.goWaits, err = newEvent() + if err != nil { + s.cWaits.Close() + return nil, err + } + return &s, nil +} + +func (s *service) close() error { + s.cWaits.Close() + s.goWaits.Close() + return nil +} + +type exitCode struct { + isSvcSpecific bool + errno uint32 +} + +func (s *service) updateStatus(status *Status, ec *exitCode) error { + if s.h == 0 { + return errors.New("updateStatus with no service status handle") + } + var t windows.SERVICE_STATUS + t.ServiceType = windows.SERVICE_WIN32_OWN_PROCESS + t.CurrentState = uint32(status.State) + if status.Accepts&AcceptStop != 0 { + t.ControlsAccepted |= windows.SERVICE_ACCEPT_STOP + } + if status.Accepts&AcceptShutdown != 0 { + t.ControlsAccepted |= windows.SERVICE_ACCEPT_SHUTDOWN + } + if status.Accepts&AcceptPauseAndContinue != 0 { + t.ControlsAccepted |= windows.SERVICE_ACCEPT_PAUSE_CONTINUE + } + if status.Accepts&AcceptParamChange != 0 { + t.ControlsAccepted |= windows.SERVICE_ACCEPT_PARAMCHANGE + } + if status.Accepts&AcceptNetBindChange != 0 { + t.ControlsAccepted |= windows.SERVICE_ACCEPT_NETBINDCHANGE + } + if status.Accepts&AcceptHardwareProfileChange != 0 { + t.ControlsAccepted |= windows.SERVICE_ACCEPT_HARDWAREPROFILECHANGE + } + if status.Accepts&AcceptPowerEvent != 0 { + t.ControlsAccepted |= windows.SERVICE_ACCEPT_POWEREVENT + } + if status.Accepts&AcceptSessionChange != 0 { + t.ControlsAccepted |= windows.SERVICE_ACCEPT_SESSIONCHANGE + } + if ec.errno == 0 { + t.Win32ExitCode = windows.NO_ERROR + t.ServiceSpecificExitCode = windows.NO_ERROR + } else if ec.isSvcSpecific { + t.Win32ExitCode = uint32(windows.ERROR_SERVICE_SPECIFIC_ERROR) + t.ServiceSpecificExitCode = ec.errno + } else { + t.Win32ExitCode = ec.errno + t.ServiceSpecificExitCode = windows.NO_ERROR + } + t.CheckPoint = status.CheckPoint + t.WaitHint = status.WaitHint + return windows.SetServiceStatus(s.h, &t) +} + +const ( + sysErrSetServiceStatusFailed = uint32(syscall.APPLICATION_ERROR) + iota + sysErrNewThreadInCallback +) + +func (s *service) run() { + s.goWaits.Wait() + s.h = windows.Handle(ssHandle) + + var argv []*uint16 + hdr := (*unsafeheader.Slice)(unsafe.Pointer(&argv)) + hdr.Data = unsafe.Pointer(sArgv) + hdr.Len = int(sArgc) + hdr.Cap = int(sArgc) + + args := make([]string, len(argv)) + for i, a := range argv { + args[i] = windows.UTF16PtrToString(a) + } + + cmdsToHandler := make(chan ChangeRequest) + changesFromHandler := make(chan Status) + exitFromHandler := make(chan exitCode) + + go func() { + ss, errno := s.handler.Execute(args, cmdsToHandler, changesFromHandler) + exitFromHandler <- exitCode{ss, errno} + }() + + ec := exitCode{isSvcSpecific: true, errno: 0} + outcr := ChangeRequest{ + CurrentStatus: Status{State: Stopped}, + } + var outch chan ChangeRequest + inch := s.c +loop: + for { + select { + case r := <-inch: + if r.errno != 0 { + ec.errno = r.errno + break loop + } + inch = nil + outch = cmdsToHandler + outcr.Cmd = r.cmd + outcr.EventType = r.eventType + outcr.EventData = r.eventData + outcr.Context = r.context + case outch <- outcr: + inch = s.c + outch = nil + case c := <-changesFromHandler: + err := s.updateStatus(&c, &ec) + if err != nil { + // best suitable error number + ec.errno = sysErrSetServiceStatusFailed + if err2, ok := err.(syscall.Errno); ok { + ec.errno = uint32(err2) + } + break loop + } + outcr.CurrentStatus = c + case ec = <-exitFromHandler: + break loop + } + } + + s.updateStatus(&Status{State: Stopped}, &ec) + s.cWaits.Set() +} + +func newCallback(fn interface{}) (cb uintptr, err error) { + defer func() { + r := recover() + if r == nil { + return + } + cb = 0 + switch v := r.(type) { + case string: + err = errors.New(v) + case error: + err = v + default: + err = errors.New("unexpected panic in syscall.NewCallback") + } + }() + return syscall.NewCallback(fn), nil +} + +// BUG(brainman): There is no mechanism to run multiple services +// inside one single executable. Perhaps, it can be overcome by +// using RegisterServiceCtrlHandlerEx Windows api. + +// Run executes service name by calling appropriate handler function. +func Run(name string, handler Handler) error { + runtime.LockOSThread() + + tid := windows.GetCurrentThreadId() + + s, err := newService(name, handler) + if err != nil { + return err + } + + ctlHandler := func(ctl, evtype, evdata, context uintptr) uintptr { + e := ctlEvent{cmd: Cmd(ctl), eventType: uint32(evtype), eventData: evdata, context: context} + // We assume that this callback function is running on + // the same thread as Run. Nowhere in MS documentation + // I could find statement to guarantee that. So putting + // check here to verify, otherwise things will go bad + // quickly, if ignored. + i := windows.GetCurrentThreadId() + if i != tid { + e.errno = sysErrNewThreadInCallback + } + s.c <- e + // Always return NO_ERROR (0) for now. + return windows.NO_ERROR + } + + var svcmain uintptr + getServiceMain(&svcmain) + t := []windows.SERVICE_TABLE_ENTRY{ + {ServiceName: syscall.StringToUTF16Ptr(s.name), ServiceProc: svcmain}, + {ServiceName: nil, ServiceProc: 0}, + } + + goWaitsH = uintptr(s.goWaits.h) + cWaitsH = uintptr(s.cWaits.h) + sName = t[0].ServiceName + ctlHandlerExProc, err = newCallback(ctlHandler) + if err != nil { + return err + } + + go s.run() + + err = windows.StartServiceCtrlDispatcher(&t[0]) + if err != nil { + return err + } + return nil +} + +// StatusHandle returns service status handle. It is safe to call this function +// from inside the Handler.Execute because then it is guaranteed to be set. +// This code will have to change once multiple services are possible per process. +func StatusHandle() windows.Handle { + return windows.Handle(ssHandle) +} diff --git a/vendor/golang.org/x/sys/windows/svc/sys_386.s b/vendor/golang.org/x/sys/windows/svc/sys_386.s new file mode 100644 index 000000000..c8a583d73 --- /dev/null +++ b/vendor/golang.org/x/sys/windows/svc/sys_386.s @@ -0,0 +1,69 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +// func servicemain(argc uint32, argv **uint16) +TEXT ·servicemain(SB),7,$0 + MOVL argc+0(FP), AX + MOVL AX, ·sArgc(SB) + MOVL argv+4(FP), AX + MOVL AX, ·sArgv(SB) + + PUSHL BP + PUSHL BX + PUSHL SI + PUSHL DI + + SUBL $12, SP + + MOVL ·sName(SB), AX + MOVL AX, (SP) + MOVL $·servicectlhandler(SB), AX + MOVL AX, 4(SP) + // Set context to 123456 to test issue #25660. + MOVL $123456, 8(SP) + MOVL ·cRegisterServiceCtrlHandlerExW(SB), AX + MOVL SP, BP + CALL AX + MOVL BP, SP + CMPL AX, $0 + JE exit + MOVL AX, ·ssHandle(SB) + + MOVL ·goWaitsH(SB), AX + MOVL AX, (SP) + MOVL ·cSetEvent(SB), AX + MOVL SP, BP + CALL AX + MOVL BP, SP + + MOVL ·cWaitsH(SB), AX + MOVL AX, (SP) + MOVL $-1, AX + MOVL AX, 4(SP) + MOVL ·cWaitForSingleObject(SB), AX + MOVL SP, BP + CALL AX + MOVL BP, SP + +exit: + ADDL $12, SP + + POPL DI + POPL SI + POPL BX + POPL BP + + MOVL 0(SP), CX + ADDL $12, SP + JMP CX + +// I do not know why, but this seems to be the only way to call +// ctlHandlerProc on Windows 7. + +// func servicectlhandler(ctl uint32, evtype uint32, evdata uintptr, context uintptr) uintptr { +TEXT ·servicectlhandler(SB),7,$0 + MOVL ·ctlHandlerExProc(SB), CX + JMP CX diff --git a/vendor/golang.org/x/sys/windows/svc/sys_amd64.s b/vendor/golang.org/x/sys/windows/svc/sys_amd64.s new file mode 100644 index 000000000..3d4df0c3d --- /dev/null +++ b/vendor/golang.org/x/sys/windows/svc/sys_amd64.s @@ -0,0 +1,48 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +// func servicemain(argc uint32, argv **uint16) +TEXT ·servicemain(SB),7,$0 + MOVQ SP, AX + ANDQ $~15, SP // alignment as per Windows requirement + SUBQ $48, SP // room for SP and 4 args as per Windows requirement + // plus one extra word to keep stack 16 bytes aligned + MOVQ AX, 32(SP) + + MOVL CX, ·sArgc(SB) + MOVQ DX, ·sArgv(SB) + + MOVQ ·sName(SB), CX + MOVQ $·servicectlhandler(SB), DX + // BUG(pastarmovj): Figure out a way to pass in context in R8. + // Set context to 123456 to test issue #25660. + MOVQ $123456, R8 + MOVQ ·cRegisterServiceCtrlHandlerExW(SB), AX + CALL AX + CMPQ AX, $0 + JE exit + MOVQ AX, ·ssHandle(SB) + + MOVQ ·goWaitsH(SB), CX + MOVQ ·cSetEvent(SB), AX + CALL AX + + MOVQ ·cWaitsH(SB), CX + MOVQ $4294967295, DX + MOVQ ·cWaitForSingleObject(SB), AX + CALL AX + +exit: + MOVQ 32(SP), SP + RET + +// I do not know why, but this seems to be the only way to call +// ctlHandlerProc on Windows 7. + +// func ·servicectlhandler(ctl uint32, evtype uint32, evdata uintptr, context uintptr) uintptr { +TEXT ·servicectlhandler(SB),7,$0 + MOVQ ·ctlHandlerExProc(SB), AX + JMP AX diff --git a/vendor/golang.org/x/sys/windows/svc/sys_arm.s b/vendor/golang.org/x/sys/windows/svc/sys_arm.s new file mode 100644 index 000000000..33c692a8d --- /dev/null +++ b/vendor/golang.org/x/sys/windows/svc/sys_arm.s @@ -0,0 +1,38 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +#include "textflag.h" + +// func servicemain(argc uint32, argv **uint16) +TEXT ·servicemain(SB),NOSPLIT|NOFRAME,$0 + MOVM.DB.W [R4, R14], (R13) // push {r4, lr} + MOVW R13, R4 + BIC $0x7, R13 // alignment for ABI + + MOVW R0, ·sArgc(SB) + MOVW R1, ·sArgv(SB) + + MOVW ·sName(SB), R0 + MOVW ·ctlHandlerExProc(SB), R1 + MOVW $0, R2 + MOVW ·cRegisterServiceCtrlHandlerExW(SB), R3 + BL (R3) + CMP $0, R0 + BEQ exit + MOVW R0, ·ssHandle(SB) + + MOVW ·goWaitsH(SB), R0 + MOVW ·cSetEvent(SB), R1 + BL (R1) + + MOVW ·cWaitsH(SB), R0 + MOVW $-1, R1 + MOVW ·cWaitForSingleObject(SB), R2 + BL (R2) + +exit: + MOVW R4, R13 // free extra stack space + MOVM.IA.W (R13), [R4, R15] // pop {r4, pc} diff --git a/vendor/modules.txt b/vendor/modules.txt index e0606e187..3aa441b66 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1099,6 +1099,7 @@ golang.org/x/sys/internal/unsafeheader golang.org/x/sys/unix golang.org/x/sys/windows golang.org/x/sys/windows/registry +golang.org/x/sys/windows/svc # golang.org/x/text v0.3.3 golang.org/x/text/encoding golang.org/x/text/encoding/charmap diff --git a/website/data/docs-navigation.js b/website/data/docs-navigation.js index 6619e8139..bc6cb1f1b 100644 --- a/website/data/docs-navigation.js +++ b/website/data/docs-navigation.js @@ -207,6 +207,7 @@ export default [ }, { category: 'caching' }, { category: 'template' }, + { category: 'winsvc' }, ], }, '----------------', diff --git a/website/pages/docs/agent/index.mdx b/website/pages/docs/agent/index.mdx index ea4758d80..973d697b0 100644 --- a/website/pages/docs/agent/index.mdx +++ b/website/pages/docs/agent/index.mdx @@ -13,6 +13,7 @@ Vault Agent is a client daemon that provides the following features: - [Auto-Auth][autoauth] - Automatically authenticate to Vault and manage the token renewal process for locally-retrieved dynamic secrets. - [Caching][caching] - Allows client-side caching of responses containing newly created tokens and responses containing leased secrets generated off of these newly created tokens. +- [Windows Service][winsvc] - Allows running the Vault Agent as a Windows service. - [Templating][template] - Allows rendering of user supplied templates by Vault Agent, using the token generated by the Auto-Auth step. To get help, run: diff --git a/website/pages/docs/agent/winsvc/index.mdx b/website/pages/docs/agent/winsvc/index.mdx new file mode 100644 index 000000000..4628459a2 --- /dev/null +++ b/website/pages/docs/agent/winsvc/index.mdx @@ -0,0 +1,58 @@ +--- +layout: docs +page_title: Vault Agent Windows Service +sidebar_title: Windows service +description: >- + Vault Agent can be run as a Windows service. +--- + +# Vault Agent Windows Service + +Vault Agent can be run as a Windows service. In order to do this, you need to register Vault Agent with the Windows +Service Control Manager using `sc.exe`. After Vault Agent is registered, it can be started like any other Windows +service. + +**Note:** These commands should be run in a PowerShell session with Administrator capabilities. + +## Register Vault Agent as a Windows service + +Use the [`sc.exe`](https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/sc-create) program +to create a service called "Vault Agent". The `binPath` argument should include the fully qualified path to the Vault +executable, as well as any arguments required. + +```shell-session +PS C:\Windows\system32> sc.exe create "Vault Agent" binPath= "C:\vault\vault.exe agent -config=\"C:\vault\agent-config.hcl\"" +[SC] CreateService SUCCESS +``` + +Note that the spacing after the `=` in `binPath=` is intentional. + +If you receive a success message, your service is registered with the service manager. + +If you get an error, please verify the path to the binary and check the arguments, by running the contents of +`binPath=` directly in a PowerShell session and observing the results. + +## Start the Vault Agent service + +You have two ways to start the service. + +- Using the `sc.exe` command. +- Go to the Windows Service Manager, and look for **Vault Agent** in the service name column. Click the +`Start` button to start the service. + +### Example starting Vault Agent using sc.exe + + ```shell-session +PS C:\Windows\system32> sc.exe start "Vault Agent" + + SERVICE_NAME: Vault Agent + TYPE : 10 WIN32_OWN_PROCESS + STATE : 4 RUNNING + (STOPPABLE, NOT_PAUSABLE, ACCEPTS_SHUTDOWN) + WIN32_EXIT_CODE : 0 (0x0) + SERVICE_EXIT_CODE : 0 (0x0) + CHECKPOINT : 0x0 + WAIT_HINT : 0x0 + PID : 6548 + FLAGS : +```