go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/providers/os/resources/pam.go (about)

     1  // Copyright (c) Mondoo, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package resources
     5  
     6  import (
     7  	"errors"
     8  	"io"
     9  	"strconv"
    10  	"strings"
    11  
    12  	"go.mondoo.com/cnquery/checksums"
    13  	"go.mondoo.com/cnquery/llx"
    14  	"go.mondoo.com/cnquery/providers-sdk/v1/plugin"
    15  	"go.mondoo.com/cnquery/providers/os/connection/shared"
    16  	"go.mondoo.com/cnquery/providers/os/resources/pam"
    17  	"go.mondoo.com/cnquery/types"
    18  )
    19  
    20  const (
    21  	defaultPamConf = "/etc/pam.conf"
    22  	defaultPamDir  = "/etc/pam.d"
    23  )
    24  
    25  func initPamConf(runtime *plugin.Runtime, args map[string]*llx.RawData) (map[string]*llx.RawData, plugin.Resource, error) {
    26  	if x, ok := args["path"]; ok {
    27  		path, ok := x.Value.(string)
    28  		if !ok {
    29  			return nil, nil, errors.New("wrong type for 'path' it must be a string")
    30  		}
    31  
    32  		f, err := CreateResource(runtime, "file", map[string]*llx.RawData{
    33  			"path": llx.StringData(path),
    34  		})
    35  		if err != nil {
    36  			return nil, nil, err
    37  		}
    38  		args["file"] = llx.ResourceData(f, "file")
    39  		delete(args, "path")
    40  	}
    41  
    42  	return args, nil, nil
    43  }
    44  
    45  func (s *mqlPamConf) id() (string, error) {
    46  	checksum := checksums.New
    47  	for i := range s.Files.Data {
    48  		path := s.Files.Data[i].(*mqlFile).Path.Data
    49  		checksum = checksum.Add(path)
    50  	}
    51  
    52  	return checksum.String(), nil
    53  }
    54  
    55  func (se *mqlPamConfServiceEntry) id() (string, error) {
    56  	ptype := se.PamType.Data
    57  	mod := se.Module.Data
    58  	s := se.Service.Data
    59  	ln := se.LineNumber.Data
    60  	lnstr := strconv.FormatInt(ln, 10)
    61  
    62  	id := s + "/" + lnstr + "/" + ptype
    63  
    64  	// for include mod is empty
    65  	if mod != "" {
    66  		id += "/" + mod
    67  	}
    68  
    69  	return id, nil
    70  }
    71  
    72  func (s *mqlPamConf) getFiles(confPath string) ([]interface{}, error) {
    73  	// check if the pam.d directory or pam config file exists
    74  	raw, err := CreateResource(s.MqlRuntime, "file", map[string]*llx.RawData{
    75  		"path": llx.StringData(confPath),
    76  	})
    77  	if err != nil {
    78  		return nil, err
    79  	}
    80  	f := raw.(*mqlFile)
    81  	exists := f.GetExists()
    82  	if exists.Error != nil {
    83  		return nil, exists.Error
    84  	}
    85  
    86  	if !exists.Data {
    87  		return nil, errors.New(" could not load pam configuration: " + confPath)
    88  	}
    89  
    90  	perm := f.GetPermissions()
    91  	if perm.Error != nil {
    92  		return nil, perm.Error
    93  	}
    94  
    95  	if perm.Data.IsDirectory.Data {
    96  		return s.getConfDFiles(confPath)
    97  	} else {
    98  		return []interface{}{f}, nil
    99  	}
   100  }
   101  
   102  func (s *mqlPamConf) getConfDFiles(confD string) ([]interface{}, error) {
   103  	files, err := CreateResource(s.MqlRuntime, "files.find", map[string]*llx.RawData{
   104  		"from": llx.StringData(confD),
   105  		"type": llx.StringData("file"),
   106  	})
   107  	if err != nil {
   108  		return nil, err
   109  	}
   110  
   111  	res := files.(*mqlFilesFind).GetList()
   112  	return res.Data, res.Error
   113  }
   114  
   115  // GetFiles is called when the user has not provided a custom path. Otherwise files are set in the init
   116  // method and this function is never called then since the data is already cached.
   117  func (s *mqlPamConf) files() ([]interface{}, error) {
   118  	// check if the pam.d directory exists and is a directory
   119  	// according to the pam spec, pam prefers the directory if it  exists over the single file config
   120  	// see http://www.linux-pam.org/Linux-PAM-html/sag-configuration.html
   121  	raw, err := CreateResource(s.MqlRuntime, "file", map[string]*llx.RawData{
   122  		"path": llx.StringData(defaultPamDir),
   123  	})
   124  	if err != nil {
   125  		return nil, err
   126  	}
   127  	f := raw.(*mqlFile)
   128  	exist := f.GetExists()
   129  	if exist.Error != nil {
   130  		return nil, exist.Error
   131  	}
   132  
   133  	if exist.Data {
   134  		return s.getFiles(defaultPamDir)
   135  	} else {
   136  		return s.getFiles(defaultPamConf)
   137  	}
   138  }
   139  
   140  func (s *mqlPamConf) content(files []interface{}) (string, error) {
   141  	conn := s.MqlRuntime.Connection.(shared.Connection)
   142  
   143  	var res strings.Builder
   144  	var notReadyError error = nil
   145  
   146  	for i := range files {
   147  		file := files[i].(*mqlFile)
   148  
   149  		f, err := conn.FileSystem().Open(file.Path.Data)
   150  		if err != nil {
   151  			return "", err
   152  		}
   153  
   154  		raw, err := io.ReadAll(f)
   155  		f.Close()
   156  		if err != nil {
   157  			return "", err
   158  		}
   159  
   160  		res.WriteString(string(raw))
   161  		res.WriteString("\n")
   162  	}
   163  
   164  	if notReadyError != nil {
   165  		return "", notReadyError
   166  	}
   167  
   168  	return res.String(), nil
   169  }
   170  
   171  func (s *mqlPamConf) services(files []interface{}) (map[string]interface{}, error) {
   172  	conn := s.MqlRuntime.Connection.(shared.Connection)
   173  
   174  	contents := map[string]string{}
   175  	var notReadyError error = nil
   176  
   177  	for i := range files {
   178  		file := files[i].(*mqlFile)
   179  
   180  		f, err := conn.FileSystem().Open(file.Path.Data)
   181  		if err != nil {
   182  			return nil, err
   183  		}
   184  
   185  		raw, err := io.ReadAll(f)
   186  		f.Close()
   187  		if err != nil {
   188  			return nil, err
   189  		}
   190  
   191  		contents[file.Path.Data] = string(raw)
   192  	}
   193  
   194  	if notReadyError != nil {
   195  		return nil, notReadyError
   196  	}
   197  
   198  	services := map[string]interface{}{}
   199  	for basename, content := range contents {
   200  		lines := strings.Split(content, "\n")
   201  		settings := []interface{}{}
   202  		var line string
   203  		for i := range lines {
   204  			line = lines[i]
   205  
   206  			if idx := strings.Index(line, "#"); idx >= 0 {
   207  				line = line[0:idx]
   208  			}
   209  			line = strings.Trim(line, " \t\r")
   210  
   211  			if line != "" {
   212  				settings = append(settings, line)
   213  			}
   214  		}
   215  		services[basename] = settings
   216  	}
   217  
   218  	return services, nil
   219  }
   220  
   221  func (s *mqlPamConf) entries(files []interface{}) (map[string]interface{}, error) {
   222  	conn := s.MqlRuntime.Connection.(shared.Connection)
   223  
   224  	contents := map[string]string{}
   225  	var notReadyError error = nil
   226  
   227  	for i := range files {
   228  		file := files[i].(*mqlFile)
   229  
   230  		f, err := conn.FileSystem().Open(file.Path.Data)
   231  		if err != nil {
   232  			return nil, err
   233  		}
   234  
   235  		raw, err := io.ReadAll(f)
   236  		f.Close()
   237  		if err != nil {
   238  			return nil, err
   239  		}
   240  
   241  		contents[file.Path.Data] = string(raw)
   242  	}
   243  
   244  	if notReadyError != nil {
   245  		return nil, notReadyError
   246  	}
   247  
   248  	services := map[string]interface{}{}
   249  	for basename, content := range contents {
   250  		lines := strings.Split(content, "\n")
   251  		settings := []interface{}{}
   252  		var line string
   253  		for i := range lines {
   254  			line = lines[i]
   255  
   256  			entry, err := pam.ParseLine(line)
   257  			if err != nil {
   258  				return nil, err
   259  			}
   260  
   261  			// empty lines parse as empty object
   262  			if entry == nil {
   263  				continue
   264  			}
   265  
   266  			pamEntry, err := CreateResource(s.MqlRuntime, "pam.conf.serviceEntry", map[string]*llx.RawData{
   267  				"service":    llx.StringData(basename),
   268  				"lineNumber": llx.IntData(int64(i)), // Used for ID
   269  				"pamType":    llx.StringData(entry.PamType),
   270  				"control":    llx.StringData(entry.Control),
   271  				"module":     llx.StringData(entry.Module),
   272  				"options":    llx.ArrayData(entry.Options, types.String),
   273  			})
   274  			if err != nil {
   275  				return nil, err
   276  			}
   277  			settings = append(settings, pamEntry.(*mqlPamConfServiceEntry))
   278  
   279  		}
   280  
   281  		services[basename] = settings
   282  	}
   283  
   284  	return services, nil
   285  }