mirror of
https://github.com/Mic92/sops-nix.git
synced 2026-01-09 15:12:36 +08:00
fix wsl lints
This commit is contained in:
parent
975c685308
commit
4d5d1b7559
4 changed files with 115 additions and 1 deletions
|
|
@ -27,18 +27,23 @@ func SecureSymlinkChown(symlinkToCheck, expectedTarget string, owner, group int)
|
|||
defer unix.Close(fd)
|
||||
|
||||
buf := make([]byte, len(expectedTarget)+1) // oversize by one to detect trunc
|
||||
|
||||
n, err := unix.Readlinkat(fd, "", buf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't readlinkat %s", symlinkToCheck)
|
||||
}
|
||||
|
||||
if n > len(expectedTarget) || string(buf[:n]) != expectedTarget {
|
||||
return fmt.Errorf("symlink %s does not point to %s", symlinkToCheck, expectedTarget)
|
||||
}
|
||||
|
||||
stat := unix.Stat_t{}
|
||||
|
||||
err = unix.Fstat(fd, &stat)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot stat '%s': %w", symlinkToCheck, err)
|
||||
}
|
||||
|
||||
if stat.Uid == uint32(owner) && stat.Gid == uint32(group) {
|
||||
return nil // already correct
|
||||
}
|
||||
|
|
@ -62,6 +67,7 @@ func MountSecretFs(mountpoint string, keysGID int, useTmpfs bool, userMode bool)
|
|||
|
||||
fstype := "ramfs"
|
||||
fsmagic := RamfsMagic
|
||||
|
||||
if useTmpfs {
|
||||
fstype = "tmpfs"
|
||||
fsmagic = TmpfsMagic
|
||||
|
|
|
|||
|
|
@ -120,7 +120,9 @@ func (f *FormatType) UnmarshalJSON(b []byte) error {
|
|||
if err := json.Unmarshal(b, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t := FormatType(s)
|
||||
|
||||
switch t {
|
||||
case "":
|
||||
*f = Yaml
|
||||
|
|
@ -165,12 +167,15 @@ func readManifest(path string) (*manifest, error) {
|
|||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open manifest: %w", err)
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
dec := json.NewDecoder(file)
|
||||
|
||||
var m manifest
|
||||
if err := dec.Decode(&m); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse manifest: %w", err)
|
||||
}
|
||||
|
||||
return &m, nil
|
||||
}
|
||||
|
||||
|
|
@ -192,6 +197,7 @@ func createSymlink(targetFile string, path string, owner int, group int, userMod
|
|||
if err = os.Symlink(targetFile, path); err != nil {
|
||||
return fmt.Errorf("cannot create symlink '%s': %w", path, err)
|
||||
}
|
||||
|
||||
if !userMode {
|
||||
if err = SecureSymlinkChown(path, targetFile, owner, group); err != nil {
|
||||
return fmt.Errorf("cannot chown symlink '%s': %w", path, err)
|
||||
|
|
@ -201,6 +207,7 @@ func createSymlink(targetFile string, path string, owner int, group int, userMod
|
|||
} else if err != nil {
|
||||
return fmt.Errorf("cannot stat '%s': %w", path, err)
|
||||
}
|
||||
|
||||
if stat.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||
linkTarget, err := os.Readlink(path)
|
||||
if os.IsNotExist(err) {
|
||||
|
|
@ -211,6 +218,7 @@ func createSymlink(targetFile string, path string, owner int, group int, userMod
|
|||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if err := os.Remove(path); err != nil {
|
||||
return fmt.Errorf("cannot override %s: %w", path, err)
|
||||
}
|
||||
|
|
@ -223,10 +231,12 @@ func symlinkSecretsAndTemplates(targetDir string, secrets []secret, templates []
|
|||
if targetFile == secret.Path {
|
||||
continue
|
||||
}
|
||||
|
||||
parent := filepath.Dir(secret.Path)
|
||||
if err := os.MkdirAll(parent, os.ModePerm); err != nil {
|
||||
return fmt.Errorf("cannot create parent directory of '%s': %w", secret.Path, err)
|
||||
}
|
||||
|
||||
if err := createSymlink(targetFile, secret.Path, secret.owner, secret.group, userMode); err != nil {
|
||||
return fmt.Errorf("failed to symlink secret '%s': %w", secret.Path, err)
|
||||
}
|
||||
|
|
@ -237,10 +247,12 @@ func symlinkSecretsAndTemplates(targetDir string, secrets []secret, templates []
|
|||
if targetFile == template.Path {
|
||||
continue
|
||||
}
|
||||
|
||||
parent := filepath.Dir(template.Path)
|
||||
if err := os.MkdirAll(parent, os.ModePerm); err != nil {
|
||||
return fmt.Errorf("cannot create parent directory of '%s': %w", template.Path, err)
|
||||
}
|
||||
|
||||
if err := createSymlink(targetFile, template.Path, template.owner, template.group, userMode); err != nil {
|
||||
return fmt.Errorf("failed to symlink template '%s': %w", template.Path, err)
|
||||
}
|
||||
|
|
@ -256,7 +268,9 @@ type plainData struct {
|
|||
|
||||
func recurseSecretKey(keys map[string]interface{}, wantedKey string) (string, error) {
|
||||
var val interface{}
|
||||
|
||||
var ok bool
|
||||
|
||||
currentKey := wantedKey
|
||||
currentData := keys
|
||||
keyUntilNow := ""
|
||||
|
|
@ -274,23 +288,31 @@ func recurseSecretKey(keys map[string]interface{}, wantedKey string) (string, er
|
|||
}
|
||||
break
|
||||
}
|
||||
|
||||
thisKey := currentKey[:slashIndex]
|
||||
|
||||
if keyUntilNow == "" {
|
||||
keyUntilNow = thisKey
|
||||
} else {
|
||||
keyUntilNow += "/" + thisKey
|
||||
}
|
||||
|
||||
currentKey = currentKey[(slashIndex + 1):]
|
||||
val, ok = currentData[thisKey]
|
||||
|
||||
if !ok {
|
||||
return "", fmt.Errorf("the key '%s' cannot be found", keyUntilNow)
|
||||
}
|
||||
|
||||
var valWithWrongType map[interface{}]interface{}
|
||||
valWithWrongType, ok = val.(map[interface{}]interface{})
|
||||
|
||||
if !ok {
|
||||
return "", fmt.Errorf("key '%s' does not refer to a dictionary", keyUntilNow)
|
||||
}
|
||||
|
||||
currentData = make(map[string]interface{})
|
||||
|
||||
for key, value := range valWithWrongType {
|
||||
currentData[key.(string)] = value
|
||||
}
|
||||
|
|
@ -334,6 +356,7 @@ func decryptSecret(s *secret, sourceFiles map[string]plainData) error {
|
|||
return fmt.Errorf("secret of type %s in %s is not supported", s.Format, s.SopsFile)
|
||||
}
|
||||
}
|
||||
|
||||
switch s.Format {
|
||||
case Binary, Dotenv, Ini:
|
||||
s.value = sourceFile.binary
|
||||
|
|
@ -345,9 +368,11 @@ func decryptSecret(s *secret, sourceFiles map[string]plainData) error {
|
|||
if err != nil {
|
||||
return fmt.Errorf("secret %s in %s is not valid: %w", s.Name, s.SopsFile, err)
|
||||
}
|
||||
|
||||
s.value = []byte(strVal)
|
||||
}
|
||||
}
|
||||
|
||||
sourceFiles[s.SopsFile] = sourceFile
|
||||
return nil
|
||||
}
|
||||
|
|
@ -369,10 +394,12 @@ const (
|
|||
|
||||
func prepareSecretsDir(secretMountpoint string, linkName string, keysGID int, userMode bool) (*string, error) {
|
||||
var generation uint64
|
||||
|
||||
linkTarget, err := os.Readlink(linkName)
|
||||
if err == nil {
|
||||
if strings.HasPrefix(linkTarget, secretMountpoint) {
|
||||
targetBasename := filepath.Base(linkTarget)
|
||||
|
||||
generation, err = strconv.ParseUint(targetBasename, 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse %s of %s as a number: %w", targetBasename, linkTarget, err)
|
||||
|
|
@ -381,16 +408,20 @@ func prepareSecretsDir(secretMountpoint string, linkName string, keysGID int, us
|
|||
} else if !os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("cannot access %s: %w", linkName, err)
|
||||
}
|
||||
|
||||
generation++
|
||||
dir := filepath.Join(secretMountpoint, strconv.Itoa(int(generation)))
|
||||
|
||||
if _, err := os.Stat(dir); !os.IsNotExist(err) {
|
||||
if err := os.RemoveAll(dir); err != nil {
|
||||
return nil, fmt.Errorf("cannot remove existing %s: %w", dir, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := os.Mkdir(dir, os.FileMode(0o751)); err != nil {
|
||||
return nil, fmt.Errorf("mkdir(): %w", err)
|
||||
}
|
||||
|
||||
if !userMode {
|
||||
if err := os.Chown(dir, 0, int(keysGID)); err != nil {
|
||||
return nil, fmt.Errorf("cannot change owner/group of '%s' to 0/%d: %w", dir, keysGID, err)
|
||||
|
|
@ -402,11 +433,13 @@ func prepareSecretsDir(secretMountpoint string, linkName string, keysGID int, us
|
|||
func createParentDirs(parent string, target string, keysGID int, userMode bool) error {
|
||||
dirs := strings.Split(filepath.Dir(target), "/")
|
||||
pathSoFar := parent
|
||||
|
||||
for _, dir := range dirs {
|
||||
pathSoFar = filepath.Join(pathSoFar, dir)
|
||||
if err := os.MkdirAll(pathSoFar, 0o751); err != nil {
|
||||
return fmt.Errorf("cannot create directory '%s' for %s: %w", pathSoFar, filepath.Join(parent, target), err)
|
||||
}
|
||||
|
||||
if !userMode {
|
||||
if err := os.Chown(pathSoFar, 0, int(keysGID)); err != nil {
|
||||
return fmt.Errorf("cannot own directory '%s' for %s: %w", pathSoFar, filepath.Join(parent, target), err)
|
||||
|
|
@ -423,9 +456,11 @@ func writeSecrets(secretDir string, secrets []secret, keysGID int, userMode bool
|
|||
if err := createParentDirs(secretDir, secret.Name, keysGID, userMode); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.WriteFile(fp, []byte(secret.value), secret.mode); err != nil {
|
||||
return fmt.Errorf("cannot write %s: %w", fp, err)
|
||||
}
|
||||
|
||||
if !userMode {
|
||||
if err := os.Chown(fp, secret.owner, secret.group); err != nil {
|
||||
return fmt.Errorf("cannot change owner/group of '%s' to %d/%d: %w", fp, secret.owner, secret.group, err)
|
||||
|
|
@ -440,6 +475,7 @@ func lookupGroup(groupname string) (int, error) {
|
|||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to lookup 'keys' group: %w", err)
|
||||
}
|
||||
|
||||
gid, err := strconv.ParseInt(group.Gid, 10, 64)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("cannot parse keys gid %s: %w", group.Gid, err)
|
||||
|
|
@ -452,6 +488,7 @@ func lookupKeysGroup() (int, error) {
|
|||
if err1 == nil {
|
||||
return gid, nil
|
||||
}
|
||||
|
||||
gid, err2 := lookupGroup("nogroup")
|
||||
if err2 == nil {
|
||||
return gid, nil
|
||||
|
|
@ -486,7 +523,9 @@ func (app *appContext) loadSopsFile(s *secret) (*secretFile, error) {
|
|||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse dotenv of '%s': %w", s.SopsFile, err)
|
||||
}
|
||||
|
||||
keys = map[string]interface{}{}
|
||||
|
||||
for k, v := range env {
|
||||
keys[k] = v
|
||||
}
|
||||
|
|
@ -495,11 +534,11 @@ func (app *appContext) loadSopsFile(s *secret) (*secretFile, error) {
|
|||
return nil, fmt.Errorf("cannot parse json of '%s': %w", s.SopsFile, err)
|
||||
}
|
||||
case Ini:
|
||||
// TODO: we do not actually check the contents of the ini here...
|
||||
_, err := ini.Load(bytes.NewReader(cipherText))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse ini of '%s': %w", s.SopsFile, err)
|
||||
}
|
||||
// TODO: we do not actually check the contents of the ini here...
|
||||
}
|
||||
|
||||
return &secretFile{
|
||||
|
|
@ -515,6 +554,7 @@ func (app *appContext) validateSopsFile(s *secret, file *secretFile) error {
|
|||
s.Name, s.SopsFile, s.Format,
|
||||
file.firstSecret.Format, file.firstSecret.Name)
|
||||
}
|
||||
|
||||
if app.checkMode != Manifest && !(s.Format == Binary || s.Format == Dotenv || s.Format == Ini) && s.Key != "" {
|
||||
_, err := recurseSecretKey(file.keys, s.Key)
|
||||
if err != nil {
|
||||
|
|
@ -537,6 +577,7 @@ func validateOwner(owner string) (int, error) {
|
|||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to lookup user '%s': %w", owner, err)
|
||||
}
|
||||
|
||||
ownerNr, err := strconv.ParseUint(lookedUp.Uid, 10, 64)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("cannot parse uid %s: %w", lookedUp.Uid, err)
|
||||
|
|
@ -549,6 +590,7 @@ func validateGroup(group string) (int, error) {
|
|||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to lookup group '%s': %w", group, err)
|
||||
}
|
||||
|
||||
groupNr, err := strconv.ParseUint(lookedUp.Gid, 10, 64)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("cannot parse gid %s: %w", lookedUp.Gid, err)
|
||||
|
|
@ -561,6 +603,7 @@ func (app *appContext) validateSecret(secret *secret) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
secret.mode = mode
|
||||
|
||||
if app.ignorePasswd || os.Getenv("NIXOS_ACTION") == "dry-activate" {
|
||||
|
|
@ -574,6 +617,7 @@ func (app *appContext) validateSecret(secret *secret) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
secret.owner = owner
|
||||
}
|
||||
|
||||
|
|
@ -584,6 +628,7 @@ func (app *appContext) validateSecret(secret *secret) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
secret.group = group
|
||||
}
|
||||
}
|
||||
|
|
@ -602,6 +647,7 @@ func (app *appContext) validateSecret(secret *secret) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
app.secretFiles[secret.SopsFile] = *maybeFile
|
||||
|
||||
file = *maybeFile
|
||||
|
|
@ -631,6 +677,7 @@ func (app *appContext) validateTemplate(template *template) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
template.mode = mode
|
||||
|
||||
if app.ignorePasswd || os.Getenv("NIXOS_ACTION") == "dry-activate" {
|
||||
|
|
@ -644,6 +691,7 @@ func (app *appContext) validateTemplate(template *template) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
template.owner = owner
|
||||
}
|
||||
|
||||
|
|
@ -654,11 +702,13 @@ func (app *appContext) validateTemplate(template *template) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
template.group = group
|
||||
}
|
||||
}
|
||||
|
||||
var templateText string
|
||||
|
||||
if template.Content != "" {
|
||||
templateText = template.Content
|
||||
} else if template.File != "" {
|
||||
|
|
@ -666,6 +716,7 @@ func (app *appContext) validateTemplate(template *template) error {
|
|||
if err != nil {
|
||||
return fmt.Errorf("cannot read %s: %w", template.File, err)
|
||||
}
|
||||
|
||||
templateText = string(templateBytes)
|
||||
} else {
|
||||
return fmt.Errorf("neither content nor file was specified for template %s", template.Name)
|
||||
|
|
@ -684,6 +735,7 @@ func (app *appContext) validateManifest() error {
|
|||
if len(m.SSHKeyPaths) > 0 {
|
||||
return fmt.Errorf(errorFmt, "sshKeyPaths")
|
||||
}
|
||||
|
||||
if m.AgeKeyFile != "" {
|
||||
return fmt.Errorf(errorFmt, "ageKeyFile")
|
||||
}
|
||||
|
|
@ -729,7 +781,9 @@ func atomicSymlink(oldname, newname string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cleanup := true
|
||||
|
||||
defer func() {
|
||||
if cleanup {
|
||||
os.RemoveAll(d)
|
||||
|
|
@ -771,6 +825,7 @@ func pruneGenerations(secretsMountPoint, secretsDir string, keepGenerations int)
|
|||
if err != nil {
|
||||
return fmt.Errorf("cannot read %s: %w", secretsMountPoint, err)
|
||||
}
|
||||
|
||||
for _, generationName := range generations {
|
||||
generationNum, err := strconv.Atoi(generationName)
|
||||
// Not a number? Not relevant
|
||||
|
|
@ -782,6 +837,7 @@ func pruneGenerations(secretsMountPoint, secretsDir string, keepGenerations int)
|
|||
if generationNum == currentGeneration {
|
||||
continue
|
||||
}
|
||||
|
||||
if currentGeneration-keepGenerations >= generationNum {
|
||||
os.RemoveAll(path.Join(secretsMountPoint, generationName))
|
||||
}
|
||||
|
|
@ -812,6 +868,7 @@ func importSSHKeys(logcfg loggingConfig, keyPaths []string, gpgHome string) erro
|
|||
fmt.Fprintf(os.Stderr, "Cannot read ssh key '%s': %s\n", p, err)
|
||||
continue
|
||||
}
|
||||
|
||||
gpgKey, err := sshkeys.SSHPrivateKeyToPGP(sshKey)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%s\n", err)
|
||||
|
|
@ -881,10 +938,12 @@ func symlinkWalk(filename string, linkDirname string, walkFn filepath.WalkFunc)
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
info, err := os.Lstat(finalPath)
|
||||
if err != nil {
|
||||
return walkFn(path, info, err)
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
return symlinkWalk(finalPath, path, walkFn)
|
||||
}
|
||||
|
|
@ -897,6 +956,7 @@ func symlinkWalk(filename string, linkDirname string, walkFn filepath.WalkFunc)
|
|||
|
||||
func handleModifications(isDry bool, logcfg loggingConfig, symlinkPath string, secretDir string, secrets []secret, templates []template) error {
|
||||
var restart []string
|
||||
|
||||
var reload []string
|
||||
|
||||
newSecrets := make(map[string]bool)
|
||||
|
|
@ -989,7 +1049,9 @@ func handleModifications(isDry bool, logcfg loggingConfig, symlinkPath string, s
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer f.Close()
|
||||
|
||||
for _, unit := range list {
|
||||
if _, err = f.WriteString(unit + "\n"); err != nil {
|
||||
return err
|
||||
|
|
@ -998,15 +1060,18 @@ func handleModifications(isDry bool, logcfg loggingConfig, symlinkPath string, s
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var dryPrefix string
|
||||
if isDry {
|
||||
dryPrefix = "/run/nixos/dry-activation"
|
||||
} else {
|
||||
dryPrefix = "/run/nixos/activation"
|
||||
}
|
||||
|
||||
if err := writeLines(restart, dryPrefix+"-restart-list"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := writeLines(reload, dryPrefix+"-reload-list"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -1018,10 +1083,12 @@ func handleModifications(isDry bool, logcfg loggingConfig, symlinkPath string, s
|
|||
|
||||
// Find removed secrets/templates.
|
||||
symlinkRenderedPath := filepath.Join(symlinkPath, RenderedSubdir)
|
||||
|
||||
err := symlinkWalk(symlinkPath, symlinkPath, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1032,6 +1099,7 @@ func handleModifications(isDry bool, logcfg loggingConfig, symlinkPath string, s
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
isSecret := strings.HasPrefix(rel, "..")
|
||||
|
||||
if isSecret {
|
||||
|
|
@ -1041,6 +1109,7 @@ func handleModifications(isDry bool, logcfg loggingConfig, symlinkPath string, s
|
|||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
removedSecrets[path] = true
|
||||
} else {
|
||||
path = strings.TrimPrefix(path, symlinkRenderedPath+string(os.PathSeparator))
|
||||
|
|
@ -1049,6 +1118,7 @@ func handleModifications(isDry bool, logcfg loggingConfig, symlinkPath string, s
|
|||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
removedTemplates[path] = true
|
||||
}
|
||||
return nil
|
||||
|
|
@ -1064,6 +1134,7 @@ func handleModifications(isDry bool, logcfg loggingConfig, symlinkPath string, s
|
|||
if len(changed) != 1 {
|
||||
s = "s"
|
||||
}
|
||||
|
||||
if isDry {
|
||||
fmt.Printf("%s %s%s: ", dryPrefix, noun, s)
|
||||
} else {
|
||||
|
|
@ -1075,6 +1146,7 @@ func handleModifications(isDry bool, logcfg loggingConfig, symlinkPath string, s
|
|||
for key := range changed {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
|
||||
sort.Strings(keys)
|
||||
|
||||
fmt.Println(strings.Join(keys, ", "))
|
||||
|
|
@ -1104,12 +1176,14 @@ func setupGPGKeyring(logcfg loggingConfig, sshKeys []string, parentDir string) (
|
|||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot create gpg home in '%s': %w", parentDir, err)
|
||||
}
|
||||
|
||||
k := keyring{dir}
|
||||
|
||||
if err := importSSHKeys(logcfg, sshKeys, dir); err != nil {
|
||||
os.RemoveAll(dir)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
os.Setenv("GNUPGHOME", dir)
|
||||
|
||||
return &k, nil
|
||||
|
|
@ -1117,14 +1191,17 @@ func setupGPGKeyring(logcfg loggingConfig, sshKeys []string, parentDir string) (
|
|||
|
||||
func parseFlags(args []string) (*options, error) {
|
||||
var opts options
|
||||
|
||||
fs := flag.NewFlagSet(args[0], flag.ContinueOnError)
|
||||
fs.Usage = func() {
|
||||
fmt.Fprintf(flag.CommandLine.Output(), "Usage: %s [OPTION] manifest.json\n", args[0])
|
||||
fs.PrintDefaults()
|
||||
}
|
||||
|
||||
var checkMode string
|
||||
fs.StringVar(&checkMode, "check-mode", "off", `Validate configuration without installing it (possible values: "manifest","sopsfile","off")`)
|
||||
fs.BoolVar(&opts.ignorePasswd, "ignore-passwd", false, `Don't look up anything in /etc/passwd. Causes everything to be owned by root:root or the user executing the tool in user mode`)
|
||||
|
||||
if err := fs.Parse(args[1:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -1140,6 +1217,7 @@ func parseFlags(args []string) (*options, error) {
|
|||
flag.Usage()
|
||||
return nil, flag.ErrHelp
|
||||
}
|
||||
|
||||
opts.manifest = fs.Arg(0)
|
||||
return &opts, nil
|
||||
}
|
||||
|
|
@ -1147,10 +1225,12 @@ func parseFlags(args []string) (*options, error) {
|
|||
func replaceRuntimeDir(path, rundir string) (ret string) {
|
||||
parts := strings.Split(path, "%%")
|
||||
first := true
|
||||
|
||||
for _, part := range parts {
|
||||
if !first {
|
||||
ret += "%"
|
||||
}
|
||||
|
||||
first = false
|
||||
ret += strings.ReplaceAll(part, "%r", rundir)
|
||||
}
|
||||
|
|
@ -1168,6 +1248,7 @@ func writeTemplates(targetDir string, templates []template, keysGID int, userMod
|
|||
if err := os.WriteFile(fp, []byte(template.value), template.mode); err != nil {
|
||||
return fmt.Errorf("cannot write %s: %w", fp, err)
|
||||
}
|
||||
|
||||
if !userMode {
|
||||
if err := os.Chown(fp, template.owner, template.group); err != nil {
|
||||
return fmt.Errorf("cannot change owner/group of '%s' to %d/%d: %w", fp, template.owner, template.group, err)
|
||||
|
|
@ -1191,16 +1272,21 @@ func installSecrets(args []string) error {
|
|||
if manifest.UserMode {
|
||||
var rundir string
|
||||
rundir, err = RuntimeDir()
|
||||
|
||||
if opts.checkMode == Off && err != nil {
|
||||
return fmt.Errorf("cannot figure out runtime directory: %w", err)
|
||||
}
|
||||
|
||||
manifest.SecretsMountPoint = replaceRuntimeDir(manifest.SecretsMountPoint, rundir)
|
||||
manifest.SymlinkPath = replaceRuntimeDir(manifest.SymlinkPath, rundir)
|
||||
|
||||
var newSecrets []secret
|
||||
|
||||
for _, secret := range manifest.Secrets {
|
||||
secret.Path = replaceRuntimeDir(secret.Path, rundir)
|
||||
newSecrets = append(newSecrets, secret)
|
||||
}
|
||||
|
||||
manifest.Secrets = newSecrets
|
||||
}
|
||||
|
||||
|
|
@ -1238,10 +1324,12 @@ func installSecrets(args []string) error {
|
|||
|
||||
if len(manifest.SSHKeyPaths) != 0 {
|
||||
var keyring *keyring
|
||||
|
||||
keyring, err = setupGPGKeyring(manifest.Logging, manifest.SSHKeyPaths, manifest.SecretsMountPoint)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error setting up gpg keyring: %w", err)
|
||||
}
|
||||
|
||||
defer keyring.Remove()
|
||||
} else if manifest.GnupgHome != "" {
|
||||
os.Setenv("GNUPGHOME", manifest.GnupgHome)
|
||||
|
|
@ -1253,11 +1341,14 @@ func installSecrets(args []string) error {
|
|||
os.Setenv("SOPS_AGE_KEY_FILE", keyfile)
|
||||
// Create the keyfile
|
||||
var ageFile *os.File
|
||||
|
||||
ageFile, err = os.OpenFile(keyfile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot create '%s': %w", keyfile, err)
|
||||
}
|
||||
|
||||
defer ageFile.Close()
|
||||
|
||||
fmt.Fprintf(ageFile, "# generated by sops-nix at %s\n", time.Now().Format(time.RFC3339))
|
||||
|
||||
// Import SSH keys
|
||||
|
|
@ -1271,10 +1362,12 @@ func installSecrets(args []string) error {
|
|||
if manifest.AgeKeyFile != "" {
|
||||
// Read the keyfile
|
||||
var contents []byte
|
||||
|
||||
contents, err = os.ReadFile(manifest.AgeKeyFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot read keyfile '%s': %w", manifest.AgeKeyFile, err)
|
||||
}
|
||||
|
||||
// Append it to the file
|
||||
_, err = ageFile.WriteString(string(contents) + "\n")
|
||||
if err != nil {
|
||||
|
|
@ -1294,6 +1387,7 @@ func installSecrets(args []string) error {
|
|||
if err != nil {
|
||||
return fmt.Errorf("failed to prepare new secrets directory: %w", err)
|
||||
}
|
||||
|
||||
if err := writeSecrets(*secretDir, manifest.Secrets, keysGID, manifest.UserMode); err != nil {
|
||||
return fmt.Errorf("cannot write secrets: %w", err)
|
||||
}
|
||||
|
|
@ -1314,9 +1408,11 @@ func installSecrets(args []string) error {
|
|||
if err := symlinkSecretsAndTemplates(manifest.SymlinkPath, manifest.Secrets, manifest.Templates, manifest.UserMode); err != nil {
|
||||
return fmt.Errorf("failed to prepare symlinks to secret store: %w", err)
|
||||
}
|
||||
|
||||
if err := atomicSymlink(*secretDir, manifest.SymlinkPath); err != nil {
|
||||
return fmt.Errorf("cannot update secrets symlink: %w", err)
|
||||
}
|
||||
|
||||
if err := pruneGenerations(manifest.SecretsMountPoint, *secretDir, manifest.KeepGenerations); err != nil {
|
||||
return fmt.Errorf("cannot prune old secrets generations: %w", err)
|
||||
}
|
||||
|
|
@ -1329,6 +1425,7 @@ func main() {
|
|||
if errors.Is(err, flag.ErrHelp) {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stderr, "%s: %s\n", os.Args[0], err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import (
|
|||
// ok fails the test if an err is not nil.
|
||||
func ok(tb testing.TB, err error) {
|
||||
tb.Helper()
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("\033[31munexpected error: %s\033[39m\n\n", err.Error())
|
||||
tb.FailNow()
|
||||
|
|
@ -29,6 +30,7 @@ func ok(tb testing.TB, err error) {
|
|||
|
||||
func equals(tb testing.TB, exp, act interface{}) {
|
||||
tb.Helper()
|
||||
|
||||
if !reflect.DeepEqual(exp, act) {
|
||||
fmt.Printf("\033[31m\texp: %#v\n\n\tgot: %#v\033[39m\n\n", exp, act)
|
||||
tb.FailNow()
|
||||
|
|
@ -41,6 +43,7 @@ func writeManifest(t *testing.T, dir string, m *manifest) string {
|
|||
filename := path.Join(dir, "manifest.json")
|
||||
f, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0o755)
|
||||
ok(t, err)
|
||||
|
||||
encoder := json.NewEncoder(f)
|
||||
ok(t, encoder.Encode(m))
|
||||
f.Close()
|
||||
|
|
@ -66,6 +69,7 @@ func (dir testDir) Remove() {
|
|||
|
||||
func newTestDir(t *testing.T) testDir {
|
||||
t.Helper()
|
||||
|
||||
tempdir, err := os.MkdirTemp("", "symlinkDir")
|
||||
ok(t, err)
|
||||
return testDir{tempdir, path.Join(tempdir, "secrets.d"), path.Join(tempdir, "secrets")}
|
||||
|
|
@ -88,15 +92,18 @@ func TestGPG(t *testing.T) { //nolint:paralleltest
|
|||
gpgEnv := append(os.Environ(), fmt.Sprintf("GNUPGHOME=%s", gpgHome))
|
||||
|
||||
ok(t, os.Mkdir(gpgHome, os.FileMode(0o700)))
|
||||
|
||||
cmd := exec.Command("gpg", "--import", path.Join(assets, "key.asc")) // nolint:gosec
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Env = gpgEnv
|
||||
ok(t, cmd.Run())
|
||||
|
||||
stopGpgCmd := exec.Command("gpgconf", "--kill", "gpg-agent")
|
||||
stopGpgCmd.Stdout = os.Stdout
|
||||
stopGpgCmd.Stderr = os.Stderr
|
||||
stopGpgCmd.Env = gpgEnv
|
||||
|
||||
defer func() {
|
||||
if err := stopGpgCmd.Run(); err != nil {
|
||||
fmt.Printf("failed to stop gpg-agent: %s\n", err)
|
||||
|
|
@ -119,6 +126,7 @@ func TestGPG(t *testing.T) { //nolint:paralleltest
|
|||
}
|
||||
|
||||
var jsonSecret, binarySecret, dotenvSecret, iniSecret secret
|
||||
|
||||
root := "root"
|
||||
// should not create a symlink
|
||||
jsonSecret = yamlSecret
|
||||
|
|
@ -179,6 +187,7 @@ func TestGPG(t *testing.T) { //nolint:paralleltest
|
|||
equals(t, 0o400, int(yamlStat.Mode().Perm()))
|
||||
stat, success := yamlStat.Sys().(*syscall.Stat_t)
|
||||
equals(t, true, success)
|
||||
|
||||
content, err := os.ReadFile(yamlSecret.Path)
|
||||
ok(t, err)
|
||||
equals(t, "test_value", string(content))
|
||||
|
|
@ -195,6 +204,7 @@ func TestGPG(t *testing.T) { //nolint:paralleltest
|
|||
ok(t, err)
|
||||
equals(t, true, jsonStat.Mode().IsRegular())
|
||||
equals(t, 0o700, int(jsonStat.Mode().Perm()))
|
||||
|
||||
if stat, ok := jsonStat.Sys().(*syscall.Stat_t); ok {
|
||||
equals(t, 0, int(stat.Uid))
|
||||
equals(t, 0, int(stat.Gid))
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ func SSHPrivateKeyToPGP(sshPrivateKey []byte) (*openpgp.Entity, error) {
|
|||
IssuerKeyId: &gpgKey.PrimaryKey.KeyId,
|
||||
},
|
||||
}
|
||||
|
||||
err = gpgKey.Identities[uid.Id].SelfSignature.SignUserId(uid.Id, gpgKey.PrimaryKey, gpgKey.PrivateKey, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue