Skip to content

Commit fa063a2

Browse files
authored
Add FreeBSD support to libs/go/sia/util (#3093)
Add os_util_freebsd.go to provide FreeBSD-specific OS utility functions for the SIA (Service Identity Agent) library. This change enables AthenZ's SIA library to build and run on FreeBSD systems by providing the platform-specific implementations previously only available for Linux, Darwin, and Windows. The FreeBSD implementation is based on the Linux version, as both systems share similar Unix semantics for: - File ownership management (os.Chown) - User/group ID lookup (id command) - Syslog functionality - Syscall operations (Setuid/Setgid) This was identified while building recent github.com/redpanda-data/connect, which is part of FreeBSD ports. Tested on: FreeBSD 14.2 amd64 Signed-off-by: Giacomo Olgeni <[email protected]>
1 parent 9af8939 commit fa063a2

File tree

1 file changed

+213
-0
lines changed

1 file changed

+213
-0
lines changed
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
package util
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"io/fs"
7+
"log"
8+
"log/syslog"
9+
"os"
10+
"os/exec"
11+
"path/filepath"
12+
"strconv"
13+
"strings"
14+
"syscall"
15+
)
16+
17+
const siaUnixGroup = "athenz"
18+
19+
func NewSysLogger() (io.Writer, error) {
20+
return syslog.New(syslog.LOG_INFO|syslog.LOG_DAEMON, "siad")
21+
}
22+
23+
func UpdateFile(fileName string, contents []byte, uid, gid int, perm os.FileMode, directUpdate, verbose bool) error {
24+
err := UpdateFileContents(fileName, contents, perm, directUpdate, verbose)
25+
if err != nil {
26+
return err
27+
}
28+
currentUid, currentGid := uidGidForUser("")
29+
if currentUid != uid || currentGid != gid {
30+
log.Printf("Changing file %s ownership to %d:%d...\n", fileName, uid, gid)
31+
err = os.Chown(fileName, uid, gid)
32+
if err != nil {
33+
log.Printf("Cannot chown file %s to %d:%d, err: %v\n", fileName, uid, gid, err)
34+
return err
35+
}
36+
}
37+
return nil
38+
}
39+
40+
func SvcAttrs(username, groupname string) (int, int, int) {
41+
// Default file mode for service key
42+
fileMode := 0400
43+
// Get uid and gid for the username.
44+
uid, gid := uidGidForUser(username)
45+
46+
// Override the group id if user explicitly specified the group.
47+
ggid := -1
48+
if groupname != "" {
49+
ggid = gidForGroup(groupname)
50+
}
51+
// if the group is not specified or invalid then we'll default
52+
// to our unix group name called athenz
53+
if ggid == -1 {
54+
ggid = gidForGroup(siaUnixGroup)
55+
}
56+
// if we have a valid value then update the gid
57+
// otherwise use the user group id value
58+
if ggid != -1 {
59+
gid = ggid
60+
}
61+
if gid != -1 {
62+
fileMode = 0440
63+
}
64+
return uid, gid, fileMode
65+
}
66+
67+
func UidGidForUserGroup(username, groupname string) (int, int) {
68+
// Get uid and gid for the username.
69+
uid, gid := uidGidForUser(username)
70+
71+
// Override the group id if user explicitly specified the group.
72+
ggid := -1
73+
if groupname != "" {
74+
ggid = gidForGroup(groupname)
75+
}
76+
// if the group is not specified or invalid then we'll default
77+
// to our unix group name called athenz
78+
if ggid == -1 {
79+
ggid = gidForGroup(siaUnixGroup)
80+
}
81+
// if we have a valid value then update the gid
82+
// otherwise use the user group id value
83+
if ggid != -1 {
84+
gid = ggid
85+
}
86+
return uid, gid
87+
}
88+
89+
func gidForGroup(groupname string) int {
90+
//shelling out to id is used here because the os/user package
91+
//requires cgo, which doesn't cross-compile. we can use getent group
92+
//command but instead we opted for a simple grep for /etc/group
93+
cmdStr := fmt.Sprintf("^%s:", groupname)
94+
out, err := exec.Command(GetUtilPath("grep"), cmdStr, "/etc/group").Output()
95+
if err != nil {
96+
log.Printf("Cannot exec '%s %s /etc/group': %v\n", GetUtilPath("grep"), groupname, err)
97+
return -1
98+
}
99+
s := strings.Trim(string(out), "\n\r ")
100+
comps := strings.Split(string(out), ":")
101+
if len(comps) < 3 {
102+
log.Printf("Invalid response from grep group command: %s\n", s)
103+
return -1
104+
}
105+
//the group id should be the third value: 'group_name:password:group_id:group_list'
106+
id, err := strconv.Atoi(comps[2])
107+
if err != nil {
108+
log.Printf("Invalid response from getent group command: %s\n", s)
109+
return -1
110+
}
111+
return id
112+
}
113+
114+
func idCommand(username, arg string) int {
115+
//shelling out to id is used here because the os/user package
116+
//requires cgo, which doesn't cross-compile
117+
var out []byte
118+
var err error
119+
if username == "" {
120+
out, err = exec.Command(GetUtilPath("id"), arg).Output()
121+
} else {
122+
out, err = exec.Command(GetUtilPath("id"), arg, username).Output()
123+
}
124+
if err != nil {
125+
log.Fatalf("Cannot exec 'id %s %s': %v\n", arg, username, err)
126+
}
127+
s := strings.Trim(string(out), "\n\r ")
128+
id, err := strconv.Atoi(s)
129+
if err != nil {
130+
log.Fatalf("Unexpected UID/GID format in user record: %s\n", string(out))
131+
}
132+
return id
133+
}
134+
135+
func uidGidForUser(username string) (int, int) {
136+
uid := idCommand(username, "-u")
137+
gid := idCommand(username, "-g")
138+
return uid, gid
139+
}
140+
141+
func SetupSIADirs(siaMainDir, siaLinkDir string, ownerUid, ownerGid int) error {
142+
// Create the certs directory, if it doesn't exist
143+
certDir := fmt.Sprintf("%s/certs", siaMainDir)
144+
if !FileExists(certDir) {
145+
err := os.MkdirAll(certDir, 0755)
146+
if err != nil {
147+
return fmt.Errorf("unable to create certs dir: %q, error: %v", certDir, err)
148+
}
149+
}
150+
151+
// Create the keys directory, if it doesn't exist
152+
keyDir := fmt.Sprintf("%s/keys", siaMainDir)
153+
if !FileExists(keyDir) {
154+
err := os.MkdirAll(keyDir, 0755)
155+
if err != nil {
156+
return fmt.Errorf("unable to create keys dir: %q, error: %v", keyDir, err)
157+
}
158+
}
159+
160+
// update our main and then subdirectories
161+
changeDirectoryOwnership(siaMainDir, ownerUid, ownerGid)
162+
setupDirOwnership(certDir, ownerUid, ownerGid)
163+
setupDirOwnership(keyDir, ownerUid, ownerGid)
164+
165+
//make sure the link directory exists as well
166+
if siaLinkDir != "" && !FileExists(siaLinkDir) {
167+
err := os.Symlink(siaMainDir, siaLinkDir)
168+
if err != nil {
169+
log.Printf("Unable to symlink SIA directory '%s': %v\n", siaLinkDir, err)
170+
return nil
171+
}
172+
}
173+
return nil
174+
}
175+
176+
func changeDirectoryOwnership(path string, ownerUid, ownerGid int) error {
177+
if ownerUid == -1 && ownerGid == -1 {
178+
return nil
179+
}
180+
log.Printf("setting %s directory ownership set to %d/%d...\n", path, ownerUid, ownerGid)
181+
err := os.Chown(path, ownerUid, ownerGid)
182+
if err != nil {
183+
log.Printf("unable to update ownership: error %v\n", err)
184+
}
185+
return err
186+
}
187+
188+
func setupDirOwnership(siaDir string, ownerUid, ownerGid int) {
189+
filepath.WalkDir(siaDir, func(path string, dirEntry fs.DirEntry, err error) error {
190+
if err == nil {
191+
err = changeDirectoryOwnership(path, ownerUid, ownerGid)
192+
}
193+
return err
194+
})
195+
}
196+
197+
func SyscallSetGid(gid int) error {
198+
return syscall.Setgid(gid)
199+
}
200+
201+
func SyscallSetUid(uid int) error {
202+
return syscall.Setuid(uid)
203+
}
204+
205+
func validateScriptArguments(args []string) bool {
206+
207+
if len(args) != 0 && !strings.HasPrefix(args[0], "/") {
208+
log.Printf("script must start with fully qualified path: %s\n", args[0])
209+
return false
210+
}
211+
212+
return true
213+
}

0 commit comments

Comments
 (0)