|
| 1 | +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one |
| 2 | +// or more contributor license agreements. Licensed under the Elastic License; |
| 3 | +// you may not use this file except in compliance with the Elastic License. |
| 4 | + |
| 5 | +//go:build windows |
| 6 | + |
| 7 | +package installtest |
| 8 | + |
| 9 | +import ( |
| 10 | + "context" |
| 11 | + "fmt" |
| 12 | + "reflect" |
| 13 | + "syscall" |
| 14 | + "unsafe" |
| 15 | + |
| 16 | + "golang.org/x/sys/windows" |
| 17 | + |
| 18 | + "github.com/elastic/elastic-agent/internal/pkg/agent/install" |
| 19 | + atesting "github.com/elastic/elastic-agent/pkg/testing" |
| 20 | +) |
| 21 | + |
| 22 | +const ACCESS_ALLOWED_ACE_TYPE = 0 |
| 23 | +const ACCESS_DENIED_ACE_TYPE = 1 |
| 24 | + |
| 25 | +var ( |
| 26 | + advapi32 = syscall.NewLazyDLL("advapi32.dll") |
| 27 | + |
| 28 | + procGetAce = advapi32.NewProc("GetAce") |
| 29 | +) |
| 30 | + |
| 31 | +type accessAllowedAce struct { |
| 32 | + AceType uint8 |
| 33 | + AceFlags uint8 |
| 34 | + AceSize uint16 |
| 35 | + AccessMask uint32 |
| 36 | + SidStart uint32 |
| 37 | +} |
| 38 | + |
| 39 | +func checkPlatform(ctx context.Context, f *atesting.Fixture, topPath string, unprivileged bool) error { |
| 40 | + secInfo, err := windows.GetNamedSecurityInfo(topPath, windows.SE_FILE_OBJECT, windows.OWNER_SECURITY_INFORMATION|windows.DACL_SECURITY_INFORMATION) |
| 41 | + if err != nil { |
| 42 | + return fmt.Errorf("GetNamedSecurityInfo failed for %s: %w", topPath, err) |
| 43 | + } |
| 44 | + if !secInfo.IsValid() { |
| 45 | + return fmt.Errorf("GetNamedSecurityInfo result is not valid for %s: %w", topPath, err) |
| 46 | + } |
| 47 | + owner, _, err := secInfo.Owner() |
| 48 | + if err != nil { |
| 49 | + return fmt.Errorf("secInfo.Owner() failed for %s: %w", topPath, err) |
| 50 | + } |
| 51 | + sids, err := getAllowedSIDs(secInfo) |
| 52 | + if err != nil { |
| 53 | + return fmt.Errorf("failed to get allowed SID's for %s: %w", topPath, err) |
| 54 | + } |
| 55 | + if unprivileged { |
| 56 | + // Check that the elastic-agent user/group exist. |
| 57 | + uid, err := install.FindUID(install.ElasticUsername) |
| 58 | + if err != nil { |
| 59 | + return fmt.Errorf("failed to find %s user: %w", install.ElasticUsername, err) |
| 60 | + } |
| 61 | + uidSID, err := windows.StringToSid(uid) |
| 62 | + if err != nil { |
| 63 | + return fmt.Errorf("failed to convert string to windows.SID %s: %w", uid, err) |
| 64 | + } |
| 65 | + gid, err := install.FindGID(install.ElasticGroupName) |
| 66 | + if err != nil { |
| 67 | + return fmt.Errorf("failed to find %s group: %w", install.ElasticGroupName, err) |
| 68 | + } |
| 69 | + gidSID, err := windows.StringToSid(gid) |
| 70 | + if err != nil { |
| 71 | + return fmt.Errorf("failed to convert string to windows.SID %s: %w", uid, err) |
| 72 | + } |
| 73 | + if !owner.Equals(uidSID) { |
| 74 | + return fmt.Errorf("%s not owned by %s user", topPath, install.ElasticUsername) |
| 75 | + } |
| 76 | + if !hasSID(sids, uidSID) { |
| 77 | + return fmt.Errorf("path %s should have ACE for %s user", topPath, install.ElasticUsername) |
| 78 | + } |
| 79 | + if !hasSID(sids, gidSID) { |
| 80 | + return fmt.Errorf("path %s should have ACE for %s group", topPath, install.ElasticGroupName) |
| 81 | + } |
| 82 | + // administrators should have access as well |
| 83 | + if !hasWellKnownSID(sids, windows.WinBuiltinAdministratorsSid) { |
| 84 | + return fmt.Errorf("path %s should have ACE for Administrators", topPath) |
| 85 | + } |
| 86 | + // that is 3 unique SID's, it should not have anymore |
| 87 | + if len(sids) > 3 { |
| 88 | + return fmt.Errorf("DACL has more than allowed ACE for %s", topPath) |
| 89 | + } |
| 90 | + } else { |
| 91 | + if !owner.IsWellKnown(windows.WinBuiltinAdministratorsSid) { |
| 92 | + return fmt.Errorf("%s not owned by Administrators", topPath) |
| 93 | + } |
| 94 | + // that is 1 unique SID, it should not have anymore |
| 95 | + if len(sids) > 1 { |
| 96 | + return fmt.Errorf("DACL has more than allowed ACE for %s", topPath) |
| 97 | + } |
| 98 | + } |
| 99 | + return nil |
| 100 | +} |
| 101 | + |
| 102 | +func hasSID(sids []*windows.SID, m *windows.SID) bool { |
| 103 | + for _, s := range sids { |
| 104 | + if s.Equals(m) { |
| 105 | + return true |
| 106 | + } |
| 107 | + } |
| 108 | + return false |
| 109 | +} |
| 110 | + |
| 111 | +func appendSID(sids []*windows.SID, a *windows.SID) []*windows.SID { |
| 112 | + if hasSID(sids, a) { |
| 113 | + return sids |
| 114 | + } |
| 115 | + return append(sids, a) |
| 116 | +} |
| 117 | + |
| 118 | +func hasWellKnownSID(sids []*windows.SID, m windows.WELL_KNOWN_SID_TYPE) bool { |
| 119 | + for _, s := range sids { |
| 120 | + if s.IsWellKnown(m) { |
| 121 | + return true |
| 122 | + } |
| 123 | + } |
| 124 | + return false |
| 125 | +} |
| 126 | + |
| 127 | +func getAllowedSIDs(secInfo *windows.SECURITY_DESCRIPTOR) ([]*windows.SID, error) { |
| 128 | + dacl, _, err := secInfo.DACL() |
| 129 | + if err != nil { |
| 130 | + return nil, fmt.Errorf("secInfo.DACL() failed: %w", err) |
| 131 | + } |
| 132 | + if dacl == nil { |
| 133 | + return nil, fmt.Errorf("no DACL set") |
| 134 | + } |
| 135 | + |
| 136 | + var sids []*windows.SID |
| 137 | + |
| 138 | + // sadly the ACL information is not exported so reflect is needed to get the aceCount |
| 139 | + // it's always field #3 because it's defined by the Windows API (so no real need to worry about it changing) |
| 140 | + rs := reflect.ValueOf(dacl).Elem() |
| 141 | + aceCount := rs.Field(3).Uint() |
| 142 | + for i := uint64(0); i < aceCount; i++ { |
| 143 | + ace := &accessAllowedAce{} |
| 144 | + ret, _, _ := procGetAce.Call(uintptr(unsafe.Pointer(dacl)), uintptr(i), uintptr(unsafe.Pointer(&ace))) |
| 145 | + if ret == 0 { |
| 146 | + return nil, fmt.Errorf("while getting ACE: %w", windows.GetLastError()) |
| 147 | + } |
| 148 | + if ace.AceType == ACCESS_DENIED_ACE_TYPE { |
| 149 | + // we never set denied ACE, something is wrong |
| 150 | + return nil, fmt.Errorf("denied ACE found (should not be set)") |
| 151 | + } |
| 152 | + if ace.AceType != ACCESS_ALLOWED_ACE_TYPE { |
| 153 | + // unknown ace type |
| 154 | + return nil, fmt.Errorf("unknown AceType: %d", ace.AceType) |
| 155 | + } |
| 156 | + aceSid := (*windows.SID)(unsafe.Pointer(&ace.SidStart)) |
| 157 | + sids = appendSID(sids, aceSid) |
| 158 | + } |
| 159 | + return sids, nil |
| 160 | +} |
0 commit comments