512e577c3f
- 1 - I added an explicit installer checkbox for importing sensitive secrets from `app.ini` in `templates/install.tmpl`. - 2 - I extended the installer form, submit pipeline, and final config writer so the optional import reuses `LFS_JWT_SECRET`, `INTERNAL_TOKEN`, and `oauth2.JWT_SECRET` from the uploaded `app.ini` instead of generating new values, including a submit-time fallback that re-reads the uploaded file if the checkbox was enabled after the first auto-import. - 3 - I finalized secret resolution for both direct values and `LFS_JWT_SECRET_URI` / `INTERNAL_TOKEN_URI` / `JWT_SECRET_URI` file-based references, and added regression coverage for direct imports, URI-based imports, the real `POST /import_app_ini` flow, and the persisted `app.ini` output.
322 lines
9.7 KiB
Go
322 lines
9.7 KiB
Go
// Copyright 2023 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package install
|
|
|
|
import (
|
|
"bytes"
|
|
"mime/multipart"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"code.gitea.io/gitea/models/unittest"
|
|
"code.gitea.io/gitea/modules/setting"
|
|
"code.gitea.io/gitea/modules/test"
|
|
"code.gitea.io/gitea/services/forms"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestRoutes(t *testing.T) {
|
|
defer test.MockVariableValue(&setting.InstallLock, false)()
|
|
|
|
r := Routes()
|
|
assert.NotNil(t, r)
|
|
|
|
w := httptest.NewRecorder()
|
|
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, 200, w.Code)
|
|
assert.Contains(t, w.Body.String(), `class="page-content install`)
|
|
assert.Contains(t, w.Body.String(), `name="default_language"`)
|
|
assert.Contains(t, w.Body.String(), `name="app_ini_file"`)
|
|
|
|
w = httptest.NewRecorder()
|
|
req = httptest.NewRequest(http.MethodGet, "/no-such", nil)
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, 404, w.Code)
|
|
|
|
w = httptest.NewRecorder()
|
|
req = httptest.NewRequest(http.MethodGet, "/assets/img/gitea.svg", nil)
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, 200, w.Code)
|
|
}
|
|
|
|
func TestReorderInstallLanguages(t *testing.T) {
|
|
defer test.MockVariableValue(&setting.Langs, []string{"en-US", "de-DE", "fr-FR"})()
|
|
|
|
assert.Equal(t, "en-US", resolveInstallDefaultLanguage(""))
|
|
assert.Equal(t, "en-US", resolveInstallDefaultLanguage("invalid"))
|
|
assert.Equal(t, "fr-FR", resolveInstallDefaultLanguage("fr-FR"))
|
|
assert.Equal(t, []string{"fr-FR", "en-US", "de-DE"}, reorderInstallLanguages("fr-FR"))
|
|
assert.Equal(t, []string{"en-US", "de-DE", "fr-FR"}, reorderInstallLanguages("invalid"))
|
|
}
|
|
|
|
func TestPopulateInstallFormFromConfig(t *testing.T) {
|
|
defer test.MockVariableValue(&setting.SupportedDatabaseTypes, []string{"mysql", "postgres", "sqlite3"})()
|
|
defer test.MockVariableValue(&setting.Langs, []string{"en-US", "de-DE", "fr-FR"})()
|
|
|
|
cfg, err := setting.NewConfigProviderFromData(`
|
|
APP_NAME = Imported Gitea
|
|
RUN_USER = imported-user
|
|
|
|
[database]
|
|
DB_TYPE = postgres
|
|
HOST = db.example.com:5432
|
|
USER = gitea
|
|
PASSWD = secret
|
|
NAME = giteadb
|
|
SCHEMA = custom
|
|
SSL_MODE = require
|
|
|
|
[repository]
|
|
ROOT = /srv/gitea/repos
|
|
|
|
[server]
|
|
DOMAIN = gitea.example.com
|
|
SSH_PORT = 2222
|
|
HTTP_PORT = 4000
|
|
ROOT_URL = https://gitea.example.com/
|
|
|
|
[lfs]
|
|
PATH = /srv/gitea/lfs
|
|
|
|
[log]
|
|
ROOT_PATH = /srv/gitea/log
|
|
|
|
[mailer]
|
|
ENABLED = true
|
|
SMTP_ADDR = smtp.example.com
|
|
SMTP_PORT = 587
|
|
FROM = Gitea <gitea@example.com>
|
|
USER = smtp-user
|
|
PASSWD = smtp-pass
|
|
|
|
[service]
|
|
REGISTER_EMAIL_CONFIRM = true
|
|
REGISTER_MANUAL_CONFIRM = false
|
|
ENABLE_NOTIFY_MAIL = true
|
|
ADMIN_CREATED_ACCOUNT_MODE = invite
|
|
DISABLE_REGISTRATION = false
|
|
ALLOW_ONLY_INTERNAL_REGISTRATION = true
|
|
ALLOW_ONLY_EXTERNAL_REGISTRATION = false
|
|
ENABLE_CAPTCHA = true
|
|
REQUIRE_SIGNIN_VIEW = true
|
|
DEFAULT_KEEP_EMAIL_PRIVATE = true
|
|
DEFAULT_ALLOW_CREATE_ORGANIZATION = false
|
|
DEFAULT_ENABLE_TIMETRACKING = false
|
|
NO_REPLY_ADDRESS = noreply.example.com
|
|
|
|
[openid]
|
|
ENABLE_OPENID_SIGNIN = true
|
|
ENABLE_OPENID_SIGNUP = true
|
|
|
|
[security]
|
|
ENABLE_UPDATE_CHECKER = true
|
|
PASSWORD_HASH_ALGO = pbkdf2
|
|
|
|
[admin]
|
|
ADMIN_MANAGEMENT_POLICY = super_admin_only
|
|
|
|
[i18n]
|
|
LANGS = de-DE,en-US
|
|
`)
|
|
require.NoError(t, err)
|
|
|
|
form, curDBType := newInstallFormFromSettings()
|
|
curDBType = populateInstallFormFromConfig(&form, cfg, curDBType)
|
|
|
|
assert.Equal(t, "postgres", curDBType)
|
|
assert.Equal(t, "postgres", form.DbType)
|
|
assert.Equal(t, "Imported Gitea", form.AppName)
|
|
assert.Equal(t, "imported-user", form.RunUser)
|
|
assert.Equal(t, "db.example.com:5432", form.DbHost)
|
|
assert.Equal(t, "gitea", form.DbUser)
|
|
assert.Equal(t, "secret", form.DbPasswd)
|
|
assert.Equal(t, "giteadb", form.DbName)
|
|
assert.Equal(t, "custom", form.DbSchema)
|
|
assert.Equal(t, "require", form.SSLMode)
|
|
assert.Equal(t, "/srv/gitea/repos", form.RepoRootPath)
|
|
assert.Equal(t, "/srv/gitea/lfs", form.LFSRootPath)
|
|
assert.Equal(t, "gitea.example.com", form.Domain)
|
|
assert.Equal(t, 2222, form.SSHPort)
|
|
assert.Equal(t, "4000", form.HTTPPort)
|
|
assert.Equal(t, "https://gitea.example.com/", form.AppURL)
|
|
assert.Equal(t, "/srv/gitea/log", form.LogRootPath)
|
|
assert.Equal(t, "smtp.example.com", form.SMTPAddr)
|
|
assert.Equal(t, "587", form.SMTPPort)
|
|
assert.Equal(t, "Gitea <gitea@example.com>", form.SMTPFrom)
|
|
assert.Equal(t, "smtp-user", form.SMTPUser)
|
|
assert.Equal(t, "smtp-pass", form.SMTPPasswd)
|
|
assert.Equal(t, "de-DE", form.DefaultLanguage)
|
|
assert.True(t, form.RegisterConfirm)
|
|
assert.False(t, form.RegisterManualConfirm)
|
|
assert.True(t, form.MailNotify)
|
|
assert.Equal(t, "invite", form.AdminCreatedAccountMode)
|
|
assert.Equal(t, "local_only", form.RegistrationMode)
|
|
assert.False(t, form.EnableOpenIDSignIn)
|
|
assert.False(t, form.EnableOpenIDSignUp)
|
|
assert.True(t, form.EnableCaptcha)
|
|
assert.True(t, form.RequireSignInView)
|
|
assert.True(t, form.DefaultKeepEmailPrivate)
|
|
assert.False(t, form.DefaultAllowCreateOrganization)
|
|
assert.False(t, form.DefaultEnableTimetracking)
|
|
assert.Equal(t, "noreply.example.com", form.NoReplyAddress)
|
|
assert.True(t, form.EnableUpdateChecker)
|
|
assert.Equal(t, "pbkdf2", form.PasswordAlgorithm)
|
|
assert.Equal(t, "super_admin_only", form.AdminManagementPolicy)
|
|
}
|
|
|
|
func TestPopulateInstallFormFromConfigWithSensitiveSecrets(t *testing.T) {
|
|
cfg, err := setting.NewConfigProviderFromData(`
|
|
[server]
|
|
LFS_JWT_SECRET = lfs-secret
|
|
|
|
[security]
|
|
INTERNAL_TOKEN = internal-secret
|
|
|
|
[oauth2]
|
|
JWT_SECRET = oauth-secret
|
|
`)
|
|
require.NoError(t, err)
|
|
|
|
form, curDBType := newInstallFormFromSettings()
|
|
form.ImportSensitiveSecrets = true
|
|
populateInstallFormFromConfig(&form, cfg, curDBType)
|
|
|
|
assert.Equal(t, "lfs-secret", form.ImportedLFSJWTSecret)
|
|
assert.Equal(t, "internal-secret", form.ImportedInternalToken)
|
|
assert.Equal(t, "oauth-secret", form.ImportedOAuth2JWTSecret)
|
|
}
|
|
|
|
func TestPopulateInstallFormFromConfigWithSensitiveSecretURIs(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
lfsSecretPath := filepath.Join(tmpDir, "lfs_secret")
|
|
internalTokenPath := filepath.Join(tmpDir, "internal_token")
|
|
oauthSecretPath := filepath.Join(tmpDir, "oauth_secret")
|
|
|
|
require.NoError(t, os.WriteFile(lfsSecretPath, []byte("lfs-secret-uri\n"), 0o644))
|
|
require.NoError(t, os.WriteFile(internalTokenPath, []byte("internal-secret-uri\n"), 0o644))
|
|
require.NoError(t, os.WriteFile(oauthSecretPath, []byte("oauth-secret-uri\n"), 0o644))
|
|
|
|
cfg, err := setting.NewConfigProviderFromData(`
|
|
[server]
|
|
LFS_JWT_SECRET_URI = file:` + filepath.ToSlash(lfsSecretPath) + `
|
|
|
|
[security]
|
|
INTERNAL_TOKEN_URI = file:` + filepath.ToSlash(internalTokenPath) + `
|
|
|
|
[oauth2]
|
|
JWT_SECRET_URI = file:` + filepath.ToSlash(oauthSecretPath) + `
|
|
`)
|
|
require.NoError(t, err)
|
|
|
|
form, curDBType := newInstallFormFromSettings()
|
|
form.ImportSensitiveSecrets = true
|
|
populateInstallFormFromConfig(&form, cfg, curDBType)
|
|
|
|
assert.Equal(t, "lfs-secret-uri", form.ImportedLFSJWTSecret)
|
|
assert.Equal(t, "internal-secret-uri", form.ImportedInternalToken)
|
|
assert.Equal(t, "oauth-secret-uri", form.ImportedOAuth2JWTSecret)
|
|
}
|
|
|
|
func TestPopulateInstallSensitiveSecretsFromConfigFillsMissingValuesOnly(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
internalTokenPath := filepath.Join(tmpDir, "internal_token")
|
|
require.NoError(t, os.WriteFile(internalTokenPath, []byte("internal-secret-uri\n"), 0o644))
|
|
|
|
cfg, err := setting.NewConfigProviderFromData(`
|
|
[server]
|
|
LFS_JWT_SECRET = lfs-secret
|
|
|
|
[security]
|
|
INTERNAL_TOKEN_URI = file:` + filepath.ToSlash(internalTokenPath) + `
|
|
|
|
[oauth2]
|
|
JWT_SECRET = oauth-secret
|
|
`)
|
|
require.NoError(t, err)
|
|
|
|
form := forms.InstallForm{
|
|
ImportSensitiveSecrets: true,
|
|
ImportedLFSJWTSecret: "",
|
|
ImportedInternalToken: "",
|
|
ImportedOAuth2JWTSecret: "already-set",
|
|
}
|
|
populateInstallSensitiveSecretsFromConfig(&form, cfg)
|
|
|
|
assert.Equal(t, "lfs-secret", form.ImportedLFSJWTSecret)
|
|
assert.Equal(t, "internal-secret-uri", form.ImportedInternalToken)
|
|
assert.Equal(t, "already-set", form.ImportedOAuth2JWTSecret)
|
|
}
|
|
|
|
func TestImportAppINIWithSensitiveSecrets(t *testing.T) {
|
|
defer test.MockVariableValue(&setting.InstallLock, false)()
|
|
|
|
var body bytes.Buffer
|
|
writer := multipart.NewWriter(&body)
|
|
require.NoError(t, writer.WriteField("import_sensitive_secrets", "on"))
|
|
|
|
fileWriter, err := writer.CreateFormFile("app_ini_file", "app.ini")
|
|
require.NoError(t, err)
|
|
_, err = fileWriter.Write([]byte(`
|
|
[server]
|
|
LFS_JWT_SECRET = lfs-secret
|
|
|
|
[security]
|
|
INTERNAL_TOKEN = internal-secret
|
|
|
|
[oauth2]
|
|
JWT_SECRET = oauth-secret
|
|
`))
|
|
require.NoError(t, err)
|
|
require.NoError(t, writer.Close())
|
|
|
|
r := Routes()
|
|
w := httptest.NewRecorder()
|
|
req := httptest.NewRequest(http.MethodPost, "/import_app_ini", &body)
|
|
req.Header.Set("Content-Type", writer.FormDataContentType())
|
|
r.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
assert.Contains(t, w.Body.String(), `name="imported_lfs_jwt_secret" value="lfs-secret"`)
|
|
assert.Contains(t, w.Body.String(), `name="imported_internal_token" value="internal-secret"`)
|
|
assert.Contains(t, w.Body.String(), `name="imported_o_auth2_jwt_secret" value="oauth-secret"`)
|
|
}
|
|
|
|
func TestApplyInstallSensitiveSecretsToConfigPersistsImportedValues(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
configPath := filepath.Join(tmpDir, "app.ini")
|
|
|
|
defer test.MockVariableValue(&setting.InternalToken, "")()
|
|
|
|
cfg, err := setting.NewConfigProviderFromData("")
|
|
require.NoError(t, err)
|
|
|
|
form := forms.InstallForm{
|
|
LFSRootPath: filepath.Join(tmpDir, "lfs"),
|
|
ImportSensitiveSecrets: true,
|
|
ImportedLFSJWTSecret: "lfs-secret",
|
|
ImportedInternalToken: "internal-secret",
|
|
ImportedOAuth2JWTSecret: "oauth-secret",
|
|
}
|
|
|
|
require.NoError(t, applyInstallSensitiveSecretsToConfig(cfg, &form))
|
|
require.NoError(t, cfg.SaveTo(configPath))
|
|
|
|
data, err := os.ReadFile(configPath)
|
|
require.NoError(t, err)
|
|
content := string(data)
|
|
assert.Contains(t, content, "LFS_JWT_SECRET = lfs-secret")
|
|
assert.Contains(t, content, "INTERNAL_TOKEN = internal-secret")
|
|
assert.Contains(t, content, "JWT_SECRET = oauth-secret")
|
|
}
|
|
|
|
func TestMain(m *testing.M) {
|
|
unittest.MainTest(m)
|
|
}
|