Skip to content

Commit 6b24ada

Browse files
lafrikslunny
authored andcommitted
Add simple master key provider for secret encryption
1 parent 8765f13 commit 6b24ada

File tree

12 files changed

+326
-0
lines changed

12 files changed

+326
-0
lines changed

cmd/generate.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,14 @@
66
package cmd
77

88
import (
9+
"encoding/base64"
910
"fmt"
1011
"os"
1112

1213
"code.gitea.io/gitea/modules/generate"
14+
"code.gitea.io/gitea/modules/log"
15+
"code.gitea.io/gitea/modules/setting"
16+
"code.gitea.io/gitea/services/secrets"
1317

1418
"github.com/mattn/go-isatty"
1519
"github.com/urfave/cli"
@@ -32,6 +36,7 @@ var (
3236
microcmdGenerateInternalToken,
3337
microcmdGenerateLfsJwtSecret,
3438
microcmdGenerateSecretKey,
39+
microcmdGenerateMasterKey,
3540
},
3641
}
3742

@@ -53,6 +58,12 @@ var (
5358
Usage: "Generate a new SECRET_KEY",
5459
Action: runGenerateSecretKey,
5560
}
61+
62+
microcmdGenerateMasterKey = cli.Command{
63+
Name: "MASTER_KEY",
64+
Usage: "Generate a new MASTER_KEY",
65+
Action: runGenerateMasterKey,
66+
}
5667
)
5768

5869
func runGenerateInternalToken(c *cli.Context) error {
@@ -99,3 +110,43 @@ func runGenerateSecretKey(c *cli.Context) error {
99110

100111
return nil
101112
}
113+
114+
func runGenerateMasterKey(c *cli.Context) error {
115+
// Silence the console logger
116+
log.DelNamedLogger("console")
117+
log.DelNamedLogger(log.DEFAULT)
118+
119+
// Read configuration file
120+
setting.LoadFromExisting()
121+
122+
providerType := secrets.MasterKeyProviderType(setting.MasterKeyProvider)
123+
if providerType == secrets.MasterKeyProviderTypeNone {
124+
return fmt.Errorf("configured master key provider does not support key generation")
125+
}
126+
127+
if err := secrets.Init(); err != nil {
128+
return err
129+
}
130+
131+
scrts, err := secrets.GenerateMasterKey()
132+
if err != nil {
133+
return err
134+
}
135+
136+
if len(scrts) > 1 {
137+
fmt.Println("Unseal secrets:")
138+
for i, secret := range scrts {
139+
if i > 0 {
140+
fmt.Printf("\n")
141+
}
142+
fmt.Printf("%s\n", base64.StdEncoding.EncodeToString(secret))
143+
}
144+
}
145+
fmt.Println("Setting changes required:")
146+
fmt.Println("[secrets]")
147+
if providerType == secrets.MasterKeyProviderTypePlain && len(scrts) == 1 {
148+
fmt.Printf("MASTER_KEY = %s\n", base64.StdEncoding.EncodeToString(scrts[0]))
149+
}
150+
151+
return nil
152+
}

modules/generate/generate.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"crypto/rand"
1010
"encoding/base64"
1111
"io"
12+
"math/big"
1213
"time"
1314

1415
"code.gitea.io/gitea/modules/util"
@@ -67,3 +68,23 @@ func NewSecretKey() (string, error) {
6768

6869
return secretKey, nil
6970
}
71+
72+
// NewMasterKey generate a new value intended to be used by MASTER_KEY.
73+
func NewMasterKey() ([]byte, error) {
74+
secretBytes := make([]byte, 32)
75+
_, err := io.ReadFull(rand.Reader, secretBytes)
76+
if err != nil {
77+
return nil, err
78+
}
79+
80+
return secretBytes, nil
81+
}
82+
83+
func randomInt(max *big.Int) (int, error) {
84+
rand, err := rand.Int(rand.Reader, max)
85+
if err != nil {
86+
return 0, err
87+
}
88+
89+
return int(rand.Int64()), nil
90+
}

modules/setting/setting.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,8 @@ var (
213213
HMACKey string `ini:"HMAC_KEY"`
214214
Allways bool
215215
}{}
216+
MasterKeyProvider string
217+
MasterKey []byte
216218

217219
// UI settings
218220
UI = struct {
@@ -953,6 +955,20 @@ func loadFromConf(allowEmpty bool, extraConfig string) {
953955
PasswordCheckPwn = sec.Key("PASSWORD_CHECK_PWN").MustBool(false)
954956
SuccessfulTokensCacheSize = sec.Key("SUCCESSFUL_TOKENS_CACHE_SIZE").MustInt(20)
955957

958+
// Master key provider configuration
959+
MasterKeyProvider = sec.Key("MASTER_KEY_PROVIDER").MustString("none")
960+
switch MasterKeyProvider {
961+
case "plain":
962+
if MasterKey, err = base64.StdEncoding.DecodeString(sec.Key("MASTER_KEY").MustString("")); err != nil {
963+
log.Fatal("error loading master key: %v", err)
964+
return
965+
}
966+
case "none":
967+
default:
968+
log.Fatal("invalid master key provider type: %v", MasterKeyProvider)
969+
return
970+
}
971+
956972
InternalToken = loadSecret(sec, "INTERNAL_TOKEN_URI", "INTERNAL_TOKEN")
957973

958974
cfgdata := sec.Key("PASSWORD_COMPLEXITY").Strings(",")

modules/web/middleware/binding.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,11 @@ func GetInclude(field reflect.StructField) string {
7979
return getRuleBody(field, "Include(")
8080
}
8181

82+
// GetIn get allowed values in form tag
83+
func GetIn(field reflect.StructField) string {
84+
return getRuleBody(field, "In(")
85+
}
86+
8287
// Validate validate TODO:
8388
func Validate(errs binding.Errors, data map[string]interface{}, f Form, l translation.Locale) binding.Errors {
8489
if errs.Len() == 0 {
@@ -131,6 +136,8 @@ func Validate(errs binding.Errors, data map[string]interface{}, f Form, l transl
131136
data["ErrorMsg"] = trName + l.Tr("form.url_error", errs[0].Message)
132137
case binding.ERR_INCLUDE:
133138
data["ErrorMsg"] = trName + l.Tr("form.include_error", GetInclude(field))
139+
case binding.ERR_IN:
140+
data["ErrorMsg"] = trName + l.Tr("form.in_error", strings.Join(strings.Split(GetIn(field), ","), ", "))
134141
case validation.ErrGlobPattern:
135142
data["ErrorMsg"] = trName + l.Tr("form.glob_pattern_error", errs[0].Message)
136143
case validation.ErrRegexPattern:

options/locale/locale_en-US.ini

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,12 @@ app_url_helper = Base address for HTTP(S) clone URLs and email notifications.
176176
log_root_path = Log Path
177177
log_root_path_helper = Log files will be written to this directory.
178178

179+
security_title = Security Settings
180+
master_key_provider = Master Key Provider
181+
master_key_provider_none = None
182+
master_key_provider_plain = Plain
183+
master_key_provider_helper = Master Key Provider to use to store secret key that will be used for other secret encryption. Use "None" to not encrypt secrets. Use "Plain" to store automatically generated secret in configuration file.
184+
179185
optional_title = Optional Settings
180186
email_title = Email Settings
181187
smtp_addr = SMTP Host
@@ -234,6 +240,7 @@ no_reply_address = Hidden Email Domain
234240
no_reply_address_helper = Domain name for users with a hidden email address. For example, the username 'joe' will be logged in Git as 'joe@noreply.example.org' if the hidden email domain is set to 'noreply.example.org'.
235241
password_algorithm = Password Hash Algorithm
236242
password_algorithm_helper = Set the password hashing algorithm. Algorithms have differing requirements and strength. `argon2` whilst having good characteristics uses a lot of memory and may be inappropriate for small systems.
243+
master_key_failed = Failed to generate master key: %v
237244

238245
[home]
239246
uname_holder = Username or Email Address
@@ -447,6 +454,7 @@ max_size_error = ` must contain at most %s characters.`
447454
email_error = ` is not a valid email address.`
448455
url_error = `'%s' is not a valid URL.`
449456
include_error = ` must contain substring '%s'.`
457+
in_error = ` can contain only specific values: %s.`
450458
glob_pattern_error = ` glob pattern is invalid: %s.`
451459
regex_pattern_error = ` regex pattern is invalid: %s.`
452460
unknown_error = Unknown error:

routers/install/install.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package install
77

88
import (
99
goctx "context"
10+
"encoding/base64"
1011
"fmt"
1112
"net/http"
1213
"os"
@@ -32,6 +33,7 @@ import (
3233
"code.gitea.io/gitea/modules/web"
3334
"code.gitea.io/gitea/modules/web/middleware"
3435
"code.gitea.io/gitea/services/forms"
36+
"code.gitea.io/gitea/services/secrets"
3537

3638
"gitea.com/go-chi/session"
3739
"gopkg.in/ini.v1"
@@ -160,6 +162,7 @@ func Install(ctx *context.Context) {
160162
form.DefaultEnableTimetracking = setting.Service.DefaultEnableTimetracking
161163
form.NoReplyAddress = setting.Service.NoReplyAddress
162164
form.PasswordAlgorithm = setting.PasswordHashAlgo
165+
form.MasterKeyProvider = secrets.MasterKeyProviderTypePlain
163166

164167
middleware.AssignForm(form, ctx.Data)
165168
ctx.HTML(http.StatusOK, tplInstall)
@@ -386,10 +389,40 @@ func SubmitInstall(ctx *context.Context) {
386389
log.Error("Failed to load custom conf '%s': %v", setting.CustomConf, err)
387390
}
388391
}
392+
393+
// Setup master key provider
394+
cfg.Section("security").Key("MASTER_KEY_PROVIDER").SetValue(string(form.MasterKeyProvider))
395+
var provider secrets.MasterKeyProvider
396+
switch form.MasterKeyProvider {
397+
case secrets.MasterKeyProviderTypePlain:
398+
provider = secrets.NewPlainMasterKeyProvider()
399+
}
400+
var masterKey []byte
401+
if provider != nil {
402+
if err = provider.Init(); err != nil {
403+
ctx.RenderWithErr(ctx.Tr("install.master_key_failed", err), tplInstall, &form)
404+
return
405+
}
406+
// Generate master key
407+
if _, err = provider.GenerateMasterKey(); err != nil {
408+
ctx.RenderWithErr(ctx.Tr("install.master_key_failed", err), tplInstall, &form)
409+
return
410+
}
411+
masterKey, err = provider.GetMasterKey()
412+
if err != nil {
413+
ctx.RenderWithErr(ctx.Tr("install.master_key_failed", err), tplInstall, &form)
414+
return
415+
}
416+
if form.MasterKeyProvider == secrets.MasterKeyProviderTypePlain {
417+
cfg.Section("security").Key("MASTER_KEY").SetValue(base64.StdEncoding.EncodeToString(masterKey))
418+
}
419+
}
420+
389421
cfg.Section("database").Key("DB_TYPE").SetValue(setting.Database.Type)
390422
cfg.Section("database").Key("HOST").SetValue(setting.Database.Host)
391423
cfg.Section("database").Key("NAME").SetValue(setting.Database.Name)
392424
cfg.Section("database").Key("USER").SetValue(setting.Database.User)
425+
// TODO: Encrypt secret
393426
cfg.Section("database").Key("PASSWD").SetValue(setting.Database.Passwd)
394427
cfg.Section("database").Key("SCHEMA").SetValue(setting.Database.Schema)
395428
cfg.Section("database").Key("SSL_MODE").SetValue(setting.Database.SSLMode)
@@ -431,6 +464,7 @@ func SubmitInstall(ctx *context.Context) {
431464
cfg.Section("mailer").Key("SMTP_PORT").SetValue(form.SMTPPort)
432465
cfg.Section("mailer").Key("FROM").SetValue(form.SMTPFrom)
433466
cfg.Section("mailer").Key("USER").SetValue(form.SMTPUser)
467+
// TODO: Encrypt secret
434468
cfg.Section("mailer").Key("PASSWD").SetValue(form.SMTPPasswd)
435469
} else {
436470
cfg.Section("mailer").Key("ENABLED").SetValue("false")

services/forms/user_form.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"code.gitea.io/gitea/modules/setting"
1515
"code.gitea.io/gitea/modules/structs"
1616
"code.gitea.io/gitea/modules/web/middleware"
17+
"code.gitea.io/gitea/services/secrets"
1718

1819
"gitea.com/go-chi/binding"
1920
)
@@ -63,6 +64,7 @@ type InstallForm struct {
6364
NoReplyAddress string
6465

6566
PasswordAlgorithm string
67+
MasterKeyProvider secrets.MasterKeyProviderType `binding:"Required;In(none,plain)"`
6668

6769
AdminName string `binding:"OmitEmpty;AlphaDashDot;MaxSize(30)" locale:"install.admin_name"`
6870
AdminPasswd string `binding:"OmitEmpty;MaxSize(255)" locale:"install.admin_password"`

services/secrets/masterkey.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright 2021 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package secrets
6+
7+
import (
8+
"fmt"
9+
)
10+
11+
// ErrMasterKeySealed is returned when trying to use master key that is sealed
12+
var ErrMasterKeySealed = fmt.Errorf("master key sealed")
13+
14+
// MasterKeyProvider provides master key used for encryption
15+
type MasterKeyProvider interface {
16+
Init() error
17+
18+
GenerateMasterKey() ([][]byte, error)
19+
20+
Unseal(secret []byte) error
21+
22+
Seal() error
23+
24+
IsSealed() bool
25+
26+
GetMasterKey() ([]byte, error)
27+
}

services/secrets/masterkey_nop.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright 2021 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package secrets
6+
7+
type nopMasterKeyProvider struct {
8+
}
9+
10+
// NewNopMasterKeyProvider returns master key provider that holds no master key and is always unsealed
11+
func NewNopMasterKeyProvider() MasterKeyProvider {
12+
return &nopMasterKeyProvider{}
13+
}
14+
15+
// Init initializes master key provider
16+
func (k *nopMasterKeyProvider) Init() error {
17+
return nil
18+
}
19+
20+
// GenerateMasterKey always returns empty master key
21+
func (k *nopMasterKeyProvider) GenerateMasterKey() ([][]byte, error) {
22+
return nil, nil
23+
}
24+
25+
// Unseal master key by providing unsealing secret
26+
func (k *nopMasterKeyProvider) Unseal(secret []byte) error {
27+
return nil
28+
}
29+
30+
// Seal master key
31+
func (k *nopMasterKeyProvider) Seal() error {
32+
return nil
33+
}
34+
35+
// IsSealed always returns false
36+
func (k *nopMasterKeyProvider) IsSealed() bool {
37+
return false
38+
}
39+
40+
// GetMasterKey returns empty master key
41+
func (k *nopMasterKeyProvider) GetMasterKey() ([]byte, error) {
42+
return nil, nil
43+
}

0 commit comments

Comments
 (0)