...

Source file src/github.com/Microsoft/hcsshim/internal/jobcontainers/logon.go

Documentation: github.com/Microsoft/hcsshim/internal/jobcontainers

     1  //go:build windows
     2  
     3  package jobcontainers
     4  
     5  import (
     6  	"context"
     7  	"fmt"
     8  	"strings"
     9  	"unsafe"
    10  
    11  	"github.com/Microsoft/go-winio/pkg/guid"
    12  	"github.com/Microsoft/hcsshim/internal/log"
    13  	"github.com/Microsoft/hcsshim/internal/winapi"
    14  	"github.com/pkg/errors"
    15  	"golang.org/x/sys/windows"
    16  )
    17  
    18  func randomPswd() (*uint16, error) {
    19  	g, err := guid.NewV4()
    20  	if err != nil {
    21  		return nil, err
    22  	}
    23  	return windows.UTF16PtrFromString(g.String())
    24  }
    25  
    26  func groupExists(groupName string) bool {
    27  	var p *byte
    28  	if err := winapi.NetLocalGroupGetInfo(
    29  		"",
    30  		groupName,
    31  		1,
    32  		&p,
    33  	); err != nil {
    34  		return false
    35  	}
    36  	defer func() {
    37  		_ = windows.NetApiBufferFree(p)
    38  	}()
    39  	return true
    40  }
    41  
    42  // makeLocalAccount creates a local account with the passed in username and a randomly generated password.
    43  // The user specified by `user` will added to the `groupName`. This function does not check if groupName exists, that must be handled
    44  // the caller.
    45  func makeLocalAccount(ctx context.Context, user, groupName string) (_ *uint16, err error) {
    46  	// Create a local account with a random password
    47  	pswd, err := randomPswd()
    48  	if err != nil {
    49  		return nil, fmt.Errorf("failed to generate random password: %w", err)
    50  	}
    51  
    52  	userUTF16, err := windows.UTF16PtrFromString(user)
    53  	if err != nil {
    54  		return nil, fmt.Errorf("failed to encode username to UTF16: %w", err)
    55  	}
    56  
    57  	usr1 := &winapi.UserInfo1{
    58  		Name:     userUTF16,
    59  		Password: pswd,
    60  		Priv:     winapi.USER_PRIV_USER,
    61  		Flags:    winapi.UF_NORMAL_ACCOUNT | winapi.UF_DONT_EXPIRE_PASSWD,
    62  	}
    63  	if err := winapi.NetUserAdd(
    64  		"",
    65  		1,
    66  		(*byte)(unsafe.Pointer(usr1)),
    67  		nil,
    68  	); err != nil {
    69  		return nil, fmt.Errorf("failed to create user %s: %w", user, err)
    70  	}
    71  	defer func() {
    72  		if err != nil {
    73  			_ = winapi.NetUserDel("", user)
    74  		}
    75  	}()
    76  
    77  	log.G(ctx).WithField("username", user).Debug("Created local user account for job container")
    78  
    79  	sid, _, _, err := windows.LookupSID("", user)
    80  	if err != nil {
    81  		return nil, fmt.Errorf("failed to lookup SID for user %q: %w", user, err)
    82  	}
    83  
    84  	sids := []winapi.LocalGroupMembersInfo0{{Sid: sid}}
    85  	if err := winapi.NetLocalGroupAddMembers(
    86  		"",
    87  		groupName,
    88  		0,
    89  		(*byte)(unsafe.Pointer(&sids[0])),
    90  		1,
    91  	); err != nil {
    92  		return nil, fmt.Errorf("failed to add user %q to the %q group: %w", user, groupName, err)
    93  	}
    94  
    95  	return pswd, nil
    96  }
    97  
    98  // processToken verifies first whether userOrGroup is a username or group name. If it's a valid group name,
    99  // a temporary local user account will be created and added to the group and then the token for the user will
   100  // be returned. If it is not a group name then the user will logged into and the token will be returned.
   101  func (c *JobContainer) processToken(ctx context.Context, userOrGroup string) (windows.Token, error) {
   102  	var (
   103  		domain   string
   104  		userName string
   105  		token    windows.Token
   106  	)
   107  
   108  	if userOrGroup == "" {
   109  		return 0, errors.New("empty username or group name passed")
   110  	}
   111  
   112  	if groupExists(userOrGroup) {
   113  		userName = c.id[:winapi.UserNameCharLimit]
   114  		pswd, err := makeLocalAccount(ctx, userName, userOrGroup)
   115  		if err != nil {
   116  			return 0, fmt.Errorf("failed to create local account for container: %w", err)
   117  		}
   118  		if err := winapi.LogonUser(
   119  			windows.StringToUTF16Ptr(userName),
   120  			nil,
   121  			pswd,
   122  			winapi.LOGON32_LOGON_INTERACTIVE,
   123  			winapi.LOGON32_PROVIDER_DEFAULT,
   124  			&token,
   125  		); err != nil {
   126  			return 0, fmt.Errorf("failed to logon user: %w", err)
   127  		}
   128  		c.localUserAccount = userName
   129  		return token, nil
   130  	}
   131  
   132  	// Must be a user string, split it by domain and username
   133  	split := strings.Split(userOrGroup, "\\")
   134  	if len(split) == 2 {
   135  		domain = split[0]
   136  		userName = split[1]
   137  	} else if len(split) == 1 {
   138  		userName = split[0]
   139  	} else {
   140  		return 0, fmt.Errorf("invalid user string `%s`", userOrGroup)
   141  	}
   142  
   143  	logonType := winapi.LOGON32_LOGON_INTERACTIVE
   144  	// User asking to run as a local system account (NETWORK SERVICE, LOCAL SERVICE, SYSTEM)
   145  	if domain == "NT AUTHORITY" {
   146  		logonType = winapi.LOGON32_LOGON_SERVICE
   147  	}
   148  
   149  	if err := winapi.LogonUser(
   150  		windows.StringToUTF16Ptr(userName),
   151  		windows.StringToUTF16Ptr(domain),
   152  		nil,
   153  		logonType,
   154  		winapi.LOGON32_PROVIDER_DEFAULT,
   155  		&token,
   156  	); err != nil {
   157  		return 0, fmt.Errorf("failed to logon user: %w", err)
   158  	}
   159  	return token, nil
   160  }
   161  
   162  func openCurrentProcessToken() (windows.Token, error) {
   163  	var token windows.Token
   164  	if err := windows.OpenProcessToken(windows.CurrentProcess(), windows.TOKEN_ALL_ACCESS, &token); err != nil {
   165  		return 0, errors.Wrap(err, "failed to open current process token")
   166  	}
   167  	return token, nil
   168  }
   169  

View as plain text