Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/git/vole.git

Write over SSH with a registered key:

git clone ssh://git@0.0.0.0:2222/vole.git

A free and open-source forge that lives in your Git repo.

Code

392 commit(s) on main

Merge change request 01M70WES5ZZDJ6RP226WB11BBZ: Let people rename and remove their own passkeys keenan · 24m ago 4b8c9e1d
history blame raw 4.5 KiB
1// Package secure creates and checks the files that hold secrets: the instance
2// directories, the account database, the SSH host key, the ACME account key and
3// the audit log.
4//
5// File modes are a security control here, not a convention. A world-readable
6// users.db is every SSH key in the roster and every passkey; a world-readable
7// host key is a forge anybody can impersonate; a world-readable audit log can be
8// read by the person it records. Creating them at 0600 is not enough on its own,
9// because a file that already exists keeps whatever mode it was created with —
10// which is how an instance upgraded from an earlier version stays exposed. So
11// creation and tightening are the same operation, and a mode that cannot be
12// tightened is an error rather than a warning: an instance that cannot protect
13// its keys must not start serving them.
14package secure
15
16import (
17 "fmt"
18 "os"
19)
20
21const (
22 // DirMode is the mode of a directory that holds secrets. It gives the service
23 // account everything and nobody else anything.
24 DirMode os.FileMode = 0o700
25 // FileMode is the mode of a secret file: the owner reads and writes, nobody
26 // else can do either.
27 FileMode os.FileMode = 0o600
28)
29
30// Mkdir creates path and any missing parents at DirMode, then removes world
31// access from a directory that already exists. Group access is left alone: a
32// packaged instance may deliberately use 0750 with a dedicated group.
33func Mkdir(path string) error {
34 if err := os.MkdirAll(path, DirMode); err != nil {
35 return err
36 }
37 _, err := HardenDir(path)
38 return err
39}
40
41// HardenDir removes world access from an existing directory and reports whether
42// it had to change anything.
43func HardenDir(path string) (bool, error) {
44 st, err := os.Stat(path)
45 if err != nil {
46 return false, err
47 }
48 if !st.IsDir() {
49 return false, fmt.Errorf("%s is not a directory", path)
50 }
51 mode := st.Mode().Perm()
52 if mode&0o007 == 0 {
53 return false, nil
54 }
55 want := mode &^ 0o007
56 if err := os.Chmod(path, want); err != nil {
57 return false, fmt.Errorf("%s is %04o and cannot be tightened to %04o: %w", path, mode, want, err)
58 }
59 return true, nil
60}
61
62// HardenFile forces an existing secret file to FileMode and reports whether it
63// had to change anything.
64func HardenFile(path string) (bool, error) {
65 st, err := os.Stat(path)
66 if err != nil {
67 return false, err
68 }
69 if st.IsDir() {
70 return false, fmt.Errorf("%s is a directory, expected a file", path)
71 }
72 if st.Mode().Perm() == FileMode {
73 return false, nil
74 }
75 had := st.Mode().Perm()
76 if err := os.Chmod(path, FileMode); err != nil {
77 return false, fmt.Errorf("%s is %04o and cannot be tightened to %04o: %w", path, had, FileMode, err)
78 }
79 return true, nil
80}
81
82// HardenFileIfExists is HardenFile for a file that may not exist yet.
83func HardenFileIfExists(path string) (bool, error) {
84 changed, err := HardenFile(path)
85 if os.IsNotExist(err) {
86 return false, nil
87 }
88 return changed, err
89}
90
91// OpenAppend opens a secret file for appending, creating it at FileMode. An
92// existing file is tightened first, so a log inherited from an older install
93// stops being readable by everyone the next time it is written.
94func OpenAppend(path string) (*os.File, error) {
95 f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, FileMode)
96 if err != nil {
97 return nil, err
98 }
99 if _, err := HardenFile(path); err != nil {
100 f.Close()
101 return nil, err
102 }
103 return f, nil
104}
105
106// WriteFile writes a secret file at FileMode, replacing whatever mode an existing
107// file had.
108func WriteFile(path string, data []byte) error {
109 if err := os.WriteFile(path, data, FileMode); err != nil {
110 return err
111 }
112 _, err := HardenFile(path)
113 return err
114}
115
116// Result is one path's outcome, so a caller can report what it changed.
117type Result struct {
118 Path string
119 Changed bool
120}
121
122// Harden applies the right mode to each path: directories lose world access,
123// files become 0600. Missing paths are skipped, because a store that has not been
124// created yet will be created at the right mode. The first failure is returned
125// with everything that did change, so the caller can still report progress.
126func Harden(paths ...string) ([]Result, error) {
127 var results []Result
128 for _, path := range paths {
129 st, err := os.Stat(path)
130 if os.IsNotExist(err) {
131 continue
132 }
133 if err != nil {
134 return results, err
135 }
136 var changed bool
137 if st.IsDir() {
138 changed, err = HardenDir(path)
139 } else {
140 changed, err = HardenFile(path)
141 }
142 if err != nil {
143 return results, err
144 }
145 if changed {
146 results = append(results, Result{Path: path, Changed: true})
147 }
148 }
149 return results, nil
150}
151