mirror of
https://github.com/go-sigma/sigma.git
synced 2026-10-28 13:34:34 +08:00
feat(config): add scm provider configuration and credential master key
The source-code providers a deployment can use were implied by auth.oauth2.*, so disabling OAuth login also disabled code repository sync and every build that clones source code, and there was nowhere to configure a self-hosted GitLab or Gitea at all: the gitea client was created with an empty base URL. Make provider capability explicit and independent from login: - add scm.providers[] with provider and baseUrl; github and gitlab default to their hosted endpoints, gitea and other self-hosted instances require a base URL and are skipped with a warning when it is missing - derive the provider list from the enabled OAuth2 providers when scm.providers is empty, so existing configurations keep working - expose Configuration.ResolveScmProviders and Configuration.ScmProvider - add auth.credentialKey for the master key that encrypts SCM credentials at rest, plus the credential.master_key settings key used as fallback conf/config.yaml, both configuration docs and the Helm chart (values and configmap) document the two new sections. Verification: - go test -run "TestConfigurationScm|TestConfigurationResolveScm" -tags "netgo,timetzdata,exclude_graphdriver_btrfs,containers_image_openpgp" ./pkg/config/... - helm template test ./deploy/sigma
This commit is contained in:
1 parent
867a26a23c
commit
642b2d576c
8 files changed
+272
No files matched your search
@@ -220,6 +220,23 @@ auth:
|
||||
lockDuration: 60s
|
||||
# inmemory | redis
|
||||
backend: inmemory
|
||||
# master key used to encrypt source code credentials at rest.
|
||||
# when empty, sigma generates one on first start and stores it in the settings table.
|
||||
# generate the key with: openssl rand -base64 32
|
||||
credentialKey: ""
|
||||
|
||||
scm:
|
||||
# source code management providers available for code repository sync and image builds.
|
||||
# this section is independent of auth.oauth2, which only decides whether a provider can be
|
||||
# used to sign in. when empty, the providers are derived from the enabled oauth2 providers.
|
||||
providers: []
|
||||
# providers:
|
||||
# - provider: github
|
||||
# baseUrl: https://github.com
|
||||
# - provider: gitlab
|
||||
# baseUrl: https://gitlab.com
|
||||
# - provider: gitea
|
||||
# baseUrl: https://gitea.example.com
|
||||
|
||||
audit:
|
||||
# record completed authenticated business HTTP requests
|
||||
|
||||
@@ -146,3 +146,7 @@ data:
|
||||
maxFailures: {{ .Values.config.auth.loginRateLimit.maxFailures | default 5 }}
|
||||
window: {{ .Values.config.auth.loginRateLimit.window | default "5m" | quote }}
|
||||
delay: {{ .Values.config.auth.loginRateLimit.delay | default "1s" | quote }}
|
||||
credentialKey: {{ .Values.config.auth.credentialKey | default "" | quote }}
|
||||
scm:
|
||||
providers:
|
||||
{{- toYaml (.Values.config.scm.providers | default list) | nindent 8 }}
|
||||
@@ -238,6 +238,19 @@ config:
|
||||
maxFailures: 5
|
||||
window: 5m
|
||||
delay: 1s
|
||||
# master key used to encrypt source code credentials at rest.
|
||||
# when empty, sigma generates one on first start and stores it in the database.
|
||||
credentialKey: ""
|
||||
scm:
|
||||
# source code management providers available for code repository sync and builds,
|
||||
# independent of the oauth2 login providers. When empty, they are derived from the
|
||||
# enabled oauth2 providers.
|
||||
# providers:
|
||||
# - provider: github
|
||||
# baseUrl: https://github.com
|
||||
# - provider: gitea
|
||||
# baseUrl: https://gitea.example.com
|
||||
providers: []
|
||||
|
||||
external:
|
||||
postgresql:
|
||||
|
||||
@@ -205,6 +205,28 @@ auth:
|
||||
window: 5m
|
||||
# fixed delay applied to every authentication attempt after maxFailures
|
||||
delay: 1s
|
||||
# master key used to encrypt source code credentials (access tokens, ssh keys,
|
||||
# passwords) at rest. when empty, sigma generates one on first start and stores it in
|
||||
# the settings table. rotating it makes the stored credentials unreadable, so users have
|
||||
# to enter them again.
|
||||
# generate the key with: openssl rand -base64 32
|
||||
credentialKey: ""
|
||||
|
||||
scm:
|
||||
# source code management providers available for code repository sync and image builds.
|
||||
# this section is independent of auth.oauth2, which only decides whether a provider can be
|
||||
# used to sign in. when empty, the providers are derived from the enabled oauth2 providers,
|
||||
# so an existing configuration keeps working.
|
||||
providers: []
|
||||
# providers:
|
||||
# - provider: github
|
||||
# baseUrl: https://github.com
|
||||
# - provider: gitlab
|
||||
# # self-hosted gitlab
|
||||
# baseUrl: https://gitlab.example.com
|
||||
# - provider: gitea
|
||||
# # gitea has no default base url and is skipped when it is empty
|
||||
# baseUrl: https://gitea.example.com
|
||||
|
||||
audit:
|
||||
# record completed authenticated business HTTP requests
|
||||
|
||||
@@ -209,6 +209,28 @@ auth:
|
||||
window: 5m
|
||||
# fixed delay applied to every authentication attempt after maxFailures
|
||||
delay: 1s
|
||||
# master key used to encrypt source code credentials (access tokens, ssh keys,
|
||||
# passwords) at rest. when empty, sigma generates one on first start and stores it in
|
||||
# the settings table. rotating it makes the stored credentials unreadable, so users have
|
||||
# to enter them again.
|
||||
# generate the key with: openssl rand -base64 32
|
||||
credentialKey: ""
|
||||
|
||||
scm:
|
||||
# source code management providers available for code repository sync and image builds.
|
||||
# this section is independent of auth.oauth2, which only decides whether a provider can be
|
||||
# used to sign in. when empty, the providers are derived from the enabled oauth2 providers,
|
||||
# so an existing configuration keeps working.
|
||||
providers: []
|
||||
# providers:
|
||||
# - provider: github
|
||||
# baseUrl: https://github.com
|
||||
# - provider: gitlab
|
||||
# # self-hosted gitlab
|
||||
# baseUrl: https://gitlab.example.com
|
||||
# - provider: gitea
|
||||
# # gitea has no default base url and is skipped when it is empty
|
||||
# baseUrl: https://gitea.example.com
|
||||
|
||||
audit:
|
||||
# record completed authenticated business HTTP requests
|
||||
|
||||
@@ -21,6 +21,8 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/go-sigma/sigma/pkg/api/enums"
|
||||
)
|
||||
|
||||
func errChecker(config Configuration) error {
|
||||
@@ -199,3 +201,91 @@ func TestConfigurationApplyEnvOverridesKeepsUnsetValues(t *testing.T) {
|
||||
require.Equal(t, "oss-ak-yaml", cfg.Storage.Oss.Ak)
|
||||
require.Equal(t, "oss-sk-yaml", cfg.Storage.Oss.Sk)
|
||||
}
|
||||
|
||||
func TestConfigurationScmWithDefaults(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
providers []ConfigurationScmProvider
|
||||
want []ConfigurationScmProvider
|
||||
}{
|
||||
{
|
||||
name: "fills hosted defaults and trims slash",
|
||||
providers: []ConfigurationScmProvider{
|
||||
{Provider: enums.ScmProviderGithub, BaseURL: "https://github.example.com/"},
|
||||
{Provider: enums.ScmProviderGitlab},
|
||||
},
|
||||
want: []ConfigurationScmProvider{
|
||||
{Provider: enums.ScmProviderGithub, BaseURL: "https://github.example.com"},
|
||||
{Provider: enums.ScmProviderGitlab, BaseURL: defaultScmBaseURLGitlab},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "drops gitea without base url",
|
||||
providers: []ConfigurationScmProvider{
|
||||
{Provider: enums.ScmProviderGitea},
|
||||
{Provider: enums.ScmProviderGithub},
|
||||
},
|
||||
want: []ConfigurationScmProvider{
|
||||
{Provider: enums.ScmProviderGithub, BaseURL: defaultScmBaseURLGithub},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "drops unknown and duplicated providers",
|
||||
providers: []ConfigurationScmProvider{
|
||||
{Provider: enums.ScmProviderNone},
|
||||
{Provider: enums.ScmProviderGithub},
|
||||
{Provider: enums.ScmProviderGithub, BaseURL: "https://github.example.com"},
|
||||
},
|
||||
want: []ConfigurationScmProvider{
|
||||
{Provider: enums.ScmProviderGithub, BaseURL: defaultScmBaseURLGithub},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
scm := ConfigurationScm{Providers: tt.providers}
|
||||
scm.WithDefaults()
|
||||
require.Equal(t, tt.want, scm.Providers)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigurationResolveScmProviders(t *testing.T) {
|
||||
t.Run("configured providers win", func(t *testing.T) {
|
||||
cfg := &Configuration{
|
||||
Auth: ConfigurationAuth{Oauth2: ConfigurationAuthOauth2{Github: ConfigurationAuthOauth2Github{Enabled: true}}},
|
||||
Scm: ConfigurationScm{Providers: []ConfigurationScmProvider{{Provider: enums.ScmProviderGitea, BaseURL: "https://gitea.example.com"}}},
|
||||
}
|
||||
require.Equal(t, cfg.Scm.Providers, cfg.ResolveScmProviders())
|
||||
})
|
||||
|
||||
t.Run("derives from oauth2 login providers", func(t *testing.T) {
|
||||
cfg := &Configuration{
|
||||
Auth: ConfigurationAuth{Oauth2: ConfigurationAuthOauth2{
|
||||
Github: ConfigurationAuthOauth2Github{Enabled: true},
|
||||
Gitlab: ConfigurationAuthOauth2Gitlab{Enabled: true},
|
||||
}},
|
||||
}
|
||||
require.Equal(t, []ConfigurationScmProvider{
|
||||
{Provider: enums.ScmProviderGithub, BaseURL: defaultScmBaseURLGithub},
|
||||
{Provider: enums.ScmProviderGitlab, BaseURL: defaultScmBaseURLGitlab},
|
||||
}, cfg.ResolveScmProviders())
|
||||
})
|
||||
|
||||
t.Run("no oauth2 login provider means no scm provider", func(t *testing.T) {
|
||||
cfg := &Configuration{}
|
||||
require.Empty(t, cfg.ResolveScmProviders())
|
||||
})
|
||||
|
||||
t.Run("ScmProvider reports enablement", func(t *testing.T) {
|
||||
cfg := &Configuration{
|
||||
Scm: ConfigurationScm{Providers: []ConfigurationScmProvider{{Provider: enums.ScmProviderGithub, BaseURL: defaultScmBaseURLGithub}}},
|
||||
}
|
||||
provider, ok := cfg.ScmProvider(enums.ProviderGithub)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, defaultScmBaseURLGithub, provider.BaseURL)
|
||||
|
||||
_, ok = cfg.ScmProvider(enums.ProviderGitlab)
|
||||
require.False(t, ok)
|
||||
})
|
||||
}
|
||||
@@ -15,6 +15,8 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -64,6 +66,7 @@ type Configuration struct {
|
||||
Proxy ConfigurationProxy `yaml:"proxy"`
|
||||
Daemon ConfigurationDaemon `yaml:"daemon"`
|
||||
Auth ConfigurationAuth `yaml:"auth"`
|
||||
Scm ConfigurationScm `yaml:"scm"`
|
||||
Audit ConfigurationAudit `yaml:"audit"`
|
||||
Trace ConfigurationTrace `yaml:"trace"`
|
||||
Analytics ConfigurationAnalytics `yaml:"analytics"`
|
||||
@@ -81,6 +84,7 @@ func (c *Configuration) WithDefaults() {
|
||||
c.Cache.WithDefaults()
|
||||
c.Locker.WithDefaults()
|
||||
c.Database.WithDefaults()
|
||||
c.Scm.WithDefaults()
|
||||
}
|
||||
|
||||
// ConfigurationAudit configures HTTP audit recording.
|
||||
@@ -707,6 +711,97 @@ func (c *ConfigurationDaemon) WithDefaults() {
|
||||
c.SizeReconcile.WithDefaults()
|
||||
}
|
||||
|
||||
// Default base URLs of the hosted source-code management providers. Gitea has no
|
||||
// default because self-hosted instances are the only sensible target.
|
||||
const (
|
||||
defaultScmBaseURLGithub = "https://github.com"
|
||||
defaultScmBaseURLGitlab = "https://gitlab.com"
|
||||
)
|
||||
|
||||
// ConfigurationScmProvider configures one source-code management provider: which
|
||||
// provider is available for code repository sync and image builds, and where it lives.
|
||||
//
|
||||
// It is intentionally separate from auth.oauth2, which only decides whether a provider
|
||||
// can be used to sign in.
|
||||
type ConfigurationScmProvider struct {
|
||||
Provider enums.ScmProvider `yaml:"provider"`
|
||||
BaseURL string `yaml:"baseUrl"`
|
||||
}
|
||||
|
||||
// ConfigurationScm configures the source-code management providers available to users.
|
||||
type ConfigurationScm struct {
|
||||
Providers []ConfigurationScmProvider `yaml:"providers"`
|
||||
}
|
||||
|
||||
// WithDefaults normalizes the configured providers: it trims trailing slashes, fills the
|
||||
// hosted default base URLs and drops entries that are not usable.
|
||||
func (c *ConfigurationScm) WithDefaults() {
|
||||
providers := make([]ConfigurationScmProvider, 0, len(c.Providers))
|
||||
for _, provider := range c.Providers {
|
||||
provider.BaseURL = strings.TrimSuffix(strings.TrimSpace(provider.BaseURL), "/")
|
||||
switch provider.Provider {
|
||||
case enums.ScmProviderGithub:
|
||||
if provider.BaseURL == "" {
|
||||
provider.BaseURL = defaultScmBaseURLGithub
|
||||
}
|
||||
case enums.ScmProviderGitlab:
|
||||
if provider.BaseURL == "" {
|
||||
provider.BaseURL = defaultScmBaseURLGitlab
|
||||
}
|
||||
case enums.ScmProviderGitea:
|
||||
if provider.BaseURL == "" {
|
||||
slog.Warn("scm provider gitea is ignored because baseUrl is empty", "provider", provider.Provider.String())
|
||||
continue
|
||||
}
|
||||
default:
|
||||
slog.Warn("scm provider is ignored because it is unknown", "provider", provider.Provider.String())
|
||||
continue
|
||||
}
|
||||
if slices.ContainsFunc(providers, func(item ConfigurationScmProvider) bool { return item.Provider == provider.Provider }) {
|
||||
slog.Warn("duplicated scm provider is ignored", "provider", provider.Provider.String())
|
||||
continue
|
||||
}
|
||||
providers = append(providers, provider)
|
||||
}
|
||||
c.Providers = providers
|
||||
}
|
||||
|
||||
// ResolveScmProviders returns the effective provider list.
|
||||
//
|
||||
// When scm.providers is empty it is derived from the enabled OAuth2 login providers, so
|
||||
// existing configurations keep working: a deployment that could sign in with GitHub or
|
||||
// GitLab could already list and clone code repositories.
|
||||
func (c *Configuration) ResolveScmProviders() []ConfigurationScmProvider {
|
||||
if len(c.Scm.Providers) > 0 {
|
||||
return c.Scm.Providers
|
||||
}
|
||||
var providers []ConfigurationScmProvider
|
||||
if c.Auth.Oauth2.Github.Enabled {
|
||||
providers = append(providers, ConfigurationScmProvider{
|
||||
Provider: enums.ScmProviderGithub,
|
||||
BaseURL: defaultScmBaseURLGithub,
|
||||
})
|
||||
}
|
||||
if c.Auth.Oauth2.Gitlab.Enabled {
|
||||
providers = append(providers, ConfigurationScmProvider{
|
||||
Provider: enums.ScmProviderGitlab,
|
||||
BaseURL: defaultScmBaseURLGitlab,
|
||||
})
|
||||
}
|
||||
return providers
|
||||
}
|
||||
|
||||
// ScmProvider returns the configuration of one provider and whether it is enabled.
|
||||
func (c *Configuration) ScmProvider(provider enums.Provider) (ConfigurationScmProvider, bool) {
|
||||
scmProvider := enums.ScmProvider(provider.String())
|
||||
for _, item := range c.ResolveScmProviders() {
|
||||
if item.Provider == scmProvider {
|
||||
return item, true
|
||||
}
|
||||
}
|
||||
return ConfigurationScmProvider{}, false
|
||||
}
|
||||
|
||||
// ConfigurationAuthInternalUser configures the built-in internal user account used by system components.
|
||||
type ConfigurationAuthInternalUser struct {
|
||||
Username string `yaml:"username"`
|
||||
@@ -783,6 +878,11 @@ type ConfigurationAuth struct {
|
||||
Oauth2 ConfigurationAuthOauth2 `yaml:"oauth2"`
|
||||
Jwt ConfigurationAuthJwt `yaml:"jwt"`
|
||||
LoginRateLimit ConfigurationAuthLoginRateLimit `yaml:"loginRateLimit"`
|
||||
// CredentialKey is the master key used to encrypt SCM credentials at rest
|
||||
// (AES-256-GCM). When empty, sigma generates a random key on first start and
|
||||
// stores it in the settings table. Rotating it makes all stored SCM
|
||||
// credentials unreadable, so they must be re-entered afterwards.
|
||||
CredentialKey string `yaml:"credentialKey"`
|
||||
}
|
||||
|
||||
// ConfigurationAuthLoginRateLimit configures failed-login throttling by username.
|
||||
|
||||
@@ -19,4 +19,8 @@ const (
|
||||
SettingSignPrivateKey = "signing.private_key"
|
||||
// SettingSignPublicKey is the public key for signing
|
||||
SettingSignPublicKey = "signing.public_key"
|
||||
// SettingCredentialMasterKey is the settings key holding the master key that encrypts
|
||||
// SCM credentials at rest. The value is generated on first start when
|
||||
// auth.credentialKey is not configured.
|
||||
SettingCredentialMasterKey = "credential.master_key" // #nosec G101 -- settings key name, not a credential
|
||||
)
|
||||
Reference in new issue
Block a user