Skip to content

Commit

Permalink
cmd/consrv: drop privileges
Browse files Browse the repository at this point in the history
Signed-off-by: Matt Layher <mdlayher@gmail.com>
  • Loading branch information
mdlayher committed Jan 17, 2024
1 parent 70565a4 commit 4cd2376
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 73 deletions.
67 changes: 36 additions & 31 deletions cmd/consrv/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,70 +161,75 @@ func main() {
}
}

privdrop := newPrivdropCond()
ids := newIdentities(cfg, ll)

// Start the SSH server.
sshListener, err := net.Listen("tcp", cfg.Server.Address)
// Start the SSH server and optional HTTP debug server.
sshl, err := net.Listen("tcp", cfg.Server.Address)
if err != nil {
ll.Fatalf("failed to listen for SSH server: %v", err)
}

sshSrv, err := newSSHServer(hostKey, devices, newIdentities(cfg, ll), ll, mm)
if err != nil {
ll.Fatalf("failed to create SSH server: %v", err)
var httpl net.Listener
if cfg.Debug.Address != "" {
l, err := net.Listen("tcp", cfg.Debug.Address)
if err != nil {
ll.Fatalf("failed to listen for HTTP debug server: %v", err)
}
httpl = l
}

if *mustPrivdrop {
// Experimental: drop privileges now that we're done reading
// configuration and opening possibly privileged TCP listeners.
info, err := dropPrivileges()
if err != nil {
ll.Fatalf("failed to drop privileges: %v", err)
}

ll.Printf("dropped privileges: chroot: %q, UID: %d GID: %d", info.Chroot, info.UID, info.GID)
}

var eg errgroup.Group

eg.Go(func() error {
defer sshListener.Close()
defer sshl.Close()

if *mustPrivdrop {
ll.Printf("SSH server waiting for privdrop")
waitForCond(privdrop)
srv, err := newSSHServer(hostKey, devices, ids, ll, mm)
if err != nil {
return fmt.Errorf("failed to create SSH server: %w", err)
}

ll.Printf("SSH server starting")
ll.Printf("starting SSH server on %q", sshListener.Addr())
if err := sshSrv.Serve(sshListener); err != nil {
ll.Printf("starting SSH server on %q", sshl.Addr())
if err := srv.Serve(sshl); err != nil {
return fmt.Errorf("failed to serve SSH: %v", err)
}

return nil
})

// Enable debug server if an address is set.
if cfg.Debug.Address != "" {
debugListener, err := net.Listen("tcp", cfg.Debug.Address)
if err != nil {
ll.Fatalf("failed to listen for HTTP debug server: %v", err)
}

if httpl != nil {
eg.Go(func() error {
defer debugListener.Close()

if *mustPrivdrop {
ll.Printf("debug HTTP waiting for privdrop")
waitForCond(privdrop)
}
defer httpl.Close()

if err := serveDebug(cfg.Debug, reg, debugListener, ll); err != nil {
if err := serveDebug(cfg.Debug, reg, httpl, ll); err != nil {
return fmt.Errorf("failed to serve debug HTTP: %v", err)
}

return nil
})
}

if *mustPrivdrop {
dropPrivileges(privdrop, ll)
}

if err := eg.Wait(); err != nil {
ll.Fatalf("failed to run: %v", err)
}
}

// privilegesInfo contains information from dropping privileges.
type privilegesInfo struct {
Chroot string
UID, GID int
}

// serveDebug starts the HTTP debug server with the input configuration.
func serveDebug(d debug, reg *prometheus.Registry, listener net.Listener, ll *log.Logger) error {
mux := http.NewServeMux()
Expand Down
15 changes: 0 additions & 15 deletions cmd/consrv/privdrop.go

This file was deleted.

53 changes: 30 additions & 23 deletions cmd/consrv/privdrop_gokrazy.go
Original file line number Diff line number Diff line change
@@ -1,45 +1,52 @@
// Copyright 2023 Berk D. Demir and Matt Layher
// Licensed 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 gokrazy

package main

import (
"log"
"fmt"
"os"
"sync"
"syscall"
)

const (
PRIVDROP_UID = 65534 // conventionally: nobody
PRIVDROP_GID = 65534 // conventionally: nogroup
CHROOT_PARENT_DIR = "/dev/shm"
CHROOT_DIR_PATTERN = "consrv-chroot-*"
privdropUID = 65534 // conventionally: nobody
privdropGID = 65534 // conventionally: nogroup
)

func dropPrivileges(cond *sync.Cond, ll *log.Logger) {
ll.Printf("droping privileges")
chrootDir, err := os.MkdirTemp(CHROOT_PARENT_DIR, CHROOT_DIR_PATTERN)
func dropPrivileges() (*privilegesInfo, error) {
dir, err := os.MkdirTemp("/dev/shm", "consrv-chroot-*")
if err != nil {
ll.Fatalf("couldn't create an empty directory under %s to chroot into: %v", CHROOT_PARENT_DIR, err)
return nil, fmt.Errorf("create chroot directory: %w", err)
}

err = syscall.Chroot(chrootDir)
if err != nil {
ll.Fatalf("couldn't chroot to %s: %v", chrootDir, err)
if err := syscall.Chroot(dir); err != nil {
return nil, fmt.Errorf("chroot %q: %w", dir, err)
}
ll.Printf("chroot'ed into %s", chrootDir)

err = syscall.Setgid(PRIVDROP_GID)
if err != nil {
ll.Fatalf("couldn't setgid to %d: %v", PRIVDROP_GID, err)
if err := syscall.Setgid(privdropGID); err != nil {
return nil, fmt.Errorf("setgid: %w", err)
}

err = syscall.Setuid(PRIVDROP_UID)
if err != nil {
ll.Fatalf("couldn't setuid to %d: %v", PRIVDROP_UID, err)
if err := syscall.Setuid(privdropUID); err != nil {
return nil, fmt.Errorf("setuid: %w", err)
}

ll.Printf("changed to uid=%d gid=%d", PRIVDROP_UID, PRIVDROP_GID)

cond.Broadcast()
return &privilegesInfo{
Chroot: dir,
UID: privdropUID,
GID: privdropGID,
}, nil
}
21 changes: 17 additions & 4 deletions cmd/consrv/privdrop_other.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@
// Copyright 2023 Berk D. Demir and Matt Layher
// Licensed 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 !gokrazy

package main

import (
"log"
"sync"
"fmt"
"runtime"
)

func dropPrivileges(_ *sync.Cond, ll *log.Logger) {
ll.Printf("Privdrop is not implemented for this platform yet")
func dropPrivileges() (*privilegesInfo, error) {
return nil, fmt.Errorf("implemented only on gokrazy, not on %s/%s", runtime.GOOS, runtime.GOARCH)
}

0 comments on commit 4cd2376

Please sign in to comment.