Skip to content

Commit

Permalink
Add daemon mode for admin-helper
Browse files Browse the repository at this point in the history
On Windows, the admin-helper service can be installed automatically with
 the command 'admin-helper install-daemon' and uninstalled with
 'admin-helper uninstall-daemon'.

It will launch the admin-helper in daemon mode and provide an HTTP API
available on the named pipe crc-admin-helper.
Only members of the Hyper-V administrators group can use it.
  • Loading branch information
guillaumerose committed May 26, 2021
1 parent 94fb5bc commit c5cfd48
Show file tree
Hide file tree
Showing 104 changed files with 25,655 additions and 4 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ GOPATH ?= $(shell go env GOPATH)
BINARY_NAME := crc-admin-helper
RELEASE_DIR ?= release

LDFLAGS := -X main.Version=$(VERSION) -extldflags='-static' -s -w $(GO_LDFLAGS)
LDFLAGS := -X github.com/code-ready/admin-helper/pkg/constants.Version=$(VERSION) -extldflags='-static' -s -w $(GO_LDFLAGS)

# Add default target
.PHONY: all
Expand Down
98 changes: 98 additions & 0 deletions cmd/admin-helper/daemon.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package main

import (
"log"
"net/http"

"github.com/code-ready/admin-helper/pkg/api"
"github.com/code-ready/admin-helper/pkg/hosts"
"github.com/kardianos/service"
"github.com/spf13/cobra"
)

var InstallDaemon = &cobra.Command{
Use: "install-daemon",
Short: "Install the daemon",
RunE: func(cmd *cobra.Command, args []string) error {
svc, err := svc()
if err != nil {
return err
}
if err := svc.Install(); err != nil {
return err
}
return svc.Start()
},
}

var UninstallDaemon = &cobra.Command{
Use: "uninstall-daemon",
Short: "Uninstall the daemon",
RunE: func(cmd *cobra.Command, args []string) error {
svc, err := svc()
if err != nil {
return err
}
if err := svc.Stop(); err != nil {
log.Println(err)
}
return svc.Uninstall()
},
}

var Daemon = &cobra.Command{
Use: "daemon",
Short: "Run as a daemon",
RunE: func(cmd *cobra.Command, args []string) error {
return daemon()
},
}

func daemon() error {
svc, err := svc()
if err != nil {
return err
}
return svc.Run()
}

func svc() (service.Service, error) {
svcConfig := &service.Config{
Name: "CodeReadyContainersAdminHelper",
DisplayName: "CodeReadyContainers Admin Helper",
Description: "Perform administrative tasks for the user",
Arguments: []string{"daemon"},
}
prg := &program{}
return service.New(prg, svcConfig)
}

type program struct{}

func (p *program) Start(s service.Service) error {
go func() {
logger, err := s.Logger(nil)
if err != nil {
log.Fatal(err)
}
ln, err := listen()
if err != nil {
_ = logger.Error(err)
return
}
hosts, err := hosts.New()
if err != nil {
_ = logger.Error(err)
return
}
if err := http.Serve(ln, api.Mux(hosts)); err != nil {
_ = logger.Error(err)
return
}
}()
return nil
}

func (p *program) Stop(s service.Service) error {
return nil
}
9 changes: 9 additions & 0 deletions cmd/admin-helper/listen_unix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// +build !windows

package main

import "net"

func listen() (net.Listener, error) {
return net.Listen("unix", "daemon.sock")
}
26 changes: 26 additions & 0 deletions cmd/admin-helper/listen_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package main

import (
"fmt"
"net"

"github.com/Microsoft/go-winio"
)

func listen() (net.Listener, error) {
// see https://github.com/moby/moby/blob/46cdcd206c56172b95ba5c77b827a722dab426c5/daemon/listeners/listeners_windows.go
// allow Administrators and SYSTEM, plus whatever additional users or groups were specified
sddl := "D:P(A;;GA;;;BA)(A;;GA;;;SY)"
sid, err := winio.LookupSidByName("crc-users")
if err != nil {
return nil, err
}
sddl += fmt.Sprintf("(A;;GRGW;;;%s)", sid)

return winio.ListenPipe(`\\.\pipe\crc-admin-helper`, &winio.PipeConfig{
SecurityDescriptor: sddl, // Administrators and system
MessageMode: true, // Use message mode so that CloseWrite() is supported
InputBufferSize: 65536, // Use 64KB buffers to improve performance
OutputBufferSize: 65536,
})
}
9 changes: 6 additions & 3 deletions cmd/admin-helper/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,23 @@ package main
import (
"fmt"
"os"
"runtime"

"github.com/code-ready/admin-helper/pkg/constants"
"github.com/spf13/cobra"
)

var Version = "dev"

func main() {
rootCmd := &cobra.Command{
Use: "admin-helper",
Version: Version,
Version: constants.Version,
SilenceUsage: true,
}

rootCmd.AddCommand(Add, Remove, Clean, Contains)
if runtime.GOOS == "windows" {
rootCmd.AddCommand(InstallDaemon, UninstallDaemon, Daemon)
}

if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
Expand Down
37 changes: 37 additions & 0 deletions cmd/sample-client/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// +build windows

package main

import (
"context"
"fmt"
"io/ioutil"
"log"
"net"
"net/http"
"time"

"github.com/Microsoft/go-winio"
)

func main() {
client := &http.Client{
Transport: &http.Transport{
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
return winio.DialPipeContext(ctx, `\\.\pipe\crc-admin-helper`)
},
},
Timeout: 5 * time.Second,
}

res, err := client.Get("http://unix/version")
if err != nil {
log.Fatal(err)
}
bin, err := ioutil.ReadAll(res.Body)
defer res.Body.Close()
if err != nil {
log.Fatal(err)
}
fmt.Println(string(bin))
}
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ module github.com/code-ready/admin-helper
go 1.14

require (
github.com/Microsoft/go-winio v0.5.0
github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/goodhosts/hostsfile v0.0.7
github.com/kardianos/service v1.2.0
github.com/spf13/cobra v1.1.1
github.com/stretchr/testify v1.3.0
)
10 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiy
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Microsoft/go-winio v0.5.0 h1:Elr9Wn+sGKPlkaBvwu4mTrxtmOp3F3yV9qhaHbXGjwU=
github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
Expand Down Expand Up @@ -103,6 +105,8 @@ github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kardianos/service v1.2.0 h1:bGuZ/epo3vrt8IPC7mnKQolqFeYJb7Cs8Rk4PSOBB/g=
github.com/kardianos/service v1.2.0/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
Expand Down Expand Up @@ -132,6 +136,7 @@ github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FI
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
Expand All @@ -153,6 +158,7 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
Expand Down Expand Up @@ -240,6 +246,10 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 h1:HyfiK1WMnHj5FXFXatD+Qs1A/xC2Run6RzeW1SyHxpc=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
Expand Down
67 changes: 67 additions & 0 deletions pkg/api/mux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package api

import (
"encoding/json"
"fmt"
"net/http"

"github.com/code-ready/admin-helper/pkg/constants"
"github.com/code-ready/admin-helper/pkg/hosts"
"github.com/code-ready/admin-helper/pkg/types"
)

func Mux(hosts *hosts.Hosts) http.Handler {
mux := http.NewServeMux()
mux.HandleFunc("/version", func(w http.ResponseWriter, r *http.Request) {
_, _ = fmt.Fprint(w, constants.Version)
})
mux.HandleFunc("/add", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "post only", http.StatusBadRequest)
return
}
var req types.AddRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if err := hosts.Add(req.IP, req.Hosts); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
})
mux.HandleFunc("/remove", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "post only", http.StatusBadRequest)
return
}
var req types.RemoveRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if err := hosts.Remove(req.Hosts); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
})
mux.HandleFunc("/clean", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "post only", http.StatusBadRequest)
return
}
var req types.CleanRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if err := hosts.Clean(req.Domains); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
})
return mux
}
21 changes: 21 additions & 0 deletions pkg/api/mux_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package api

import (
"net/http"
"net/http/httptest"
"testing"

"github.com/code-ready/admin-helper/pkg/client"
"github.com/code-ready/admin-helper/pkg/constants"
"github.com/stretchr/testify/assert"
)

func TestMux(t *testing.T) {
ts := httptest.NewServer(Mux(nil))
defer ts.Close()

client := client.New(http.DefaultClient, ts.URL)
version, err := client.Version()
assert.NoError(t, err)
assert.Equal(t, constants.Version, version)
}
Loading

0 comments on commit c5cfd48

Please sign in to comment.