mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 20:51:43 +00:00
site config: redact more secrets (#32436)
This commit is contained in:
parent
cb20941505
commit
84b896840d
@ -5,11 +5,13 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
"github.com/xeipuuv/gojsonschema"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/internal/conf/confdefaults"
|
||||
"github.com/sourcegraph/sourcegraph/internal/conf/conftypes"
|
||||
"github.com/sourcegraph/sourcegraph/internal/jsonc"
|
||||
"github.com/sourcegraph/sourcegraph/lib/errors"
|
||||
"github.com/sourcegraph/sourcegraph/schema"
|
||||
)
|
||||
|
||||
@ -203,18 +205,49 @@ func ValidateSite(input string) (messages []string, err error) {
|
||||
return problems.Messages(), nil
|
||||
}
|
||||
|
||||
// siteConfigSecrets is the list of secrets in site config needs to be redacted
|
||||
// before serving or unredacted before saving.
|
||||
var siteConfigSecrets = []struct {
|
||||
readPath string // gjson uses "." as path separator, uses "\" to escape.
|
||||
editPaths []string
|
||||
}{
|
||||
{readPath: `executors\.accessToken`, editPaths: []string{"executors.accessToken"}},
|
||||
{readPath: `email\.smtp.password`, editPaths: []string{"email.smtp", "password"}},
|
||||
{readPath: `organizationInvitations.signingKey`, editPaths: []string{"organizationInvitations", "signingKey"}},
|
||||
{readPath: `githubClientSecret`, editPaths: []string{"githubClientSecret"}},
|
||||
{readPath: `dotcom.githubApp\.cloud.clientSecret`, editPaths: []string{"dotcom", "githubApp.cloud", "clientSecret"}},
|
||||
{readPath: `dotcom.githubApp\.cloud.privateKey`, editPaths: []string{"dotcom", "githubApp.cloud", "privateKey"}},
|
||||
}
|
||||
|
||||
// UnredactSecrets unredacts unchanged secrets back to their original value for
|
||||
// the given configuration.
|
||||
//
|
||||
// Updates to this function should also being reflected in the RedactSecrets.
|
||||
func UnredactSecrets(input string, raw conftypes.RawUnified) (string, error) {
|
||||
oldCfg, err := ParseConfig(raw)
|
||||
if err != nil {
|
||||
return input, errors.Wrap(err, "parse old config")
|
||||
}
|
||||
|
||||
oldSecrets := make(map[string]string, len(oldCfg.AuthProviders))
|
||||
for _, ap := range oldCfg.AuthProviders {
|
||||
if ap.Openidconnect != nil {
|
||||
oldSecrets[ap.Openidconnect.ClientID] = ap.Openidconnect.ClientSecret
|
||||
}
|
||||
if ap.Github != nil {
|
||||
oldSecrets[ap.Github.ClientID] = ap.Github.ClientSecret
|
||||
}
|
||||
if ap.Gitlab != nil {
|
||||
oldSecrets[ap.Gitlab.ClientID] = ap.Gitlab.ClientSecret
|
||||
}
|
||||
}
|
||||
|
||||
newCfg, err := ParseConfig(conftypes.RawUnified{
|
||||
Site: input,
|
||||
})
|
||||
if err != nil {
|
||||
return input, err
|
||||
return input, errors.Wrap(err, "parse new config")
|
||||
}
|
||||
oldCfg, err := ParseConfig(raw)
|
||||
if err != nil {
|
||||
return input, err
|
||||
}
|
||||
oldSecrets := getSecretsMap(oldCfg)
|
||||
for _, ap := range newCfg.AuthProviders {
|
||||
if ap.Openidconnect != nil && ap.Openidconnect.ClientSecret == RedactedSecret {
|
||||
ap.Openidconnect.ClientSecret = oldSecrets[ap.Openidconnect.ClientID]
|
||||
@ -228,35 +261,34 @@ func UnredactSecrets(input string, raw conftypes.RawUnified) (string, error) {
|
||||
}
|
||||
unredactedSite, err := jsonc.Edit(input, newCfg.AuthProviders, "auth.providers")
|
||||
if err != nil {
|
||||
return input, err
|
||||
return input, errors.Wrap(err, `unredact "auth.providers"`)
|
||||
}
|
||||
if newCfg.ExecutorsAccessToken == RedactedSecret {
|
||||
unredactedSite, err = jsonc.Edit(unredactedSite, oldCfg.ExecutorsAccessToken, "executors.accessToken")
|
||||
|
||||
for _, secret := range siteConfigSecrets {
|
||||
v := gjson.Get(unredactedSite, secret.readPath).String()
|
||||
if v != RedactedSecret {
|
||||
continue
|
||||
}
|
||||
|
||||
unredactedSite, err = jsonc.Edit(unredactedSite, gjson.Get(raw.Site, secret.readPath).String(), secret.editPaths...)
|
||||
if err != nil {
|
||||
return input, errors.Wrapf(err, `unredact %q`, strings.Join(secret.editPaths, " > "))
|
||||
}
|
||||
}
|
||||
return unredactedSite, err
|
||||
}
|
||||
|
||||
func getSecretsMap(cfg *Unified) map[string]string {
|
||||
secretsMap := make(map[string]string, len(cfg.AuthProviders))
|
||||
for _, ap := range cfg.AuthProviders {
|
||||
if ap.Openidconnect != nil {
|
||||
secretsMap[ap.Openidconnect.ClientID] = ap.Openidconnect.ClientSecret
|
||||
}
|
||||
if ap.Github != nil {
|
||||
secretsMap[ap.Github.ClientID] = ap.Github.ClientSecret
|
||||
}
|
||||
if ap.Gitlab != nil {
|
||||
secretsMap[ap.Gitlab.ClientID] = ap.Gitlab.ClientSecret
|
||||
}
|
||||
}
|
||||
return secretsMap
|
||||
}
|
||||
|
||||
func RedactSecrets(raw conftypes.RawUnified) (conftypes.RawUnified, error) {
|
||||
// RedactSecrets redacts defined list of secrets from the given configuration. It
|
||||
// returns empty configuration if any error occurs during redacting process to
|
||||
// prevent accidental leak of secrets in the configuration.
|
||||
//
|
||||
// Updates to this function should also being reflected in the UnredactSecrets.
|
||||
func RedactSecrets(raw conftypes.RawUnified) (empty conftypes.RawUnified, err error) {
|
||||
cfg, err := ParseConfig(raw)
|
||||
if err != nil {
|
||||
return raw, err
|
||||
return empty, errors.Wrap(err, "parse config")
|
||||
}
|
||||
|
||||
for _, ap := range cfg.AuthProviders {
|
||||
if ap.Openidconnect != nil {
|
||||
ap.Openidconnect.ClientSecret = RedactedSecret
|
||||
@ -268,15 +300,25 @@ func RedactSecrets(raw conftypes.RawUnified) (conftypes.RawUnified, error) {
|
||||
ap.Gitlab.ClientSecret = RedactedSecret
|
||||
}
|
||||
}
|
||||
newSite, err := jsonc.Edit(raw.Site, cfg.AuthProviders, "auth.providers")
|
||||
redactedSite, err := jsonc.Edit(raw.Site, cfg.AuthProviders, "auth.providers")
|
||||
if err != nil {
|
||||
return raw, err
|
||||
return empty, errors.Wrap(err, `redact "auth.providers"`)
|
||||
}
|
||||
if cfg.ExecutorsAccessToken != "" {
|
||||
newSite, err = jsonc.Edit(newSite, RedactedSecret, "executors.accessToken")
|
||||
|
||||
for _, secret := range siteConfigSecrets {
|
||||
v := gjson.Get(redactedSite, secret.readPath).String()
|
||||
if v == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
redactedSite, err = jsonc.Edit(redactedSite, RedactedSecret, secret.editPaths...)
|
||||
if err != nil {
|
||||
return empty, errors.Wrapf(err, `redact %q`, strings.Join(secret.editPaths, " > "))
|
||||
}
|
||||
}
|
||||
|
||||
return conftypes.RawUnified{
|
||||
Site: newSite,
|
||||
Site: redactedSite,
|
||||
}, err
|
||||
}
|
||||
|
||||
|
||||
@ -5,14 +5,24 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/internal/conf/conftypes"
|
||||
"github.com/sourcegraph/sourcegraph/schema"
|
||||
)
|
||||
|
||||
var executorsAccessToken = "executors-access-token"
|
||||
var openIDSecret = "open-id-secret"
|
||||
var gitlabClientSecret = "gitlab-client-secret"
|
||||
var githubClientSecret = "github-client-secret"
|
||||
const (
|
||||
executorsAccessToken = "executorsAccessToken"
|
||||
authOpenIDClientSecret = "authOpenIDClientSecret"
|
||||
authGitHubClientSecret = "authGitHubClientSecret"
|
||||
authGitLabClientSecret = "authGitLabClientSecret"
|
||||
emailSMTPPassword = "emailSMTPPassword"
|
||||
organizationInvitationsSigningKey = "organizationInvitationsSigningKey"
|
||||
githubClientSecret = "githubClientSecret"
|
||||
dotcomGitHubAppCloudClientSecret = "dotcomGitHubAppCloudClientSecret"
|
||||
dotcomGitHubAppCloudPrivateKey = "dotcomGitHubAppCloudPrivateKey"
|
||||
)
|
||||
|
||||
func TestValidate(t *testing.T) {
|
||||
t.Run("valid", func(t *testing.T) {
|
||||
@ -130,75 +140,114 @@ func TestProblems(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRedact(t *testing.T) {
|
||||
t.Run("redact secrets", func(t *testing.T) {
|
||||
input := getTestSiteWithSecrets(executorsAccessToken, openIDSecret, gitlabClientSecret, githubClientSecret)
|
||||
redacted, err := RedactSecrets(conftypes.RawUnified{
|
||||
Site: input,
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error redacting secrets: %s", err)
|
||||
}
|
||||
expected := getTestSiteWithRedactedSecrets()
|
||||
if !redacted.Equal(conftypes.RawUnified{Site: expected}) {
|
||||
t.Errorf("unexpected output of RedactSecrets")
|
||||
}
|
||||
})
|
||||
func TestRedactSecrets(t *testing.T) {
|
||||
redacted, err := RedactSecrets(
|
||||
conftypes.RawUnified{
|
||||
Site: getTestSiteWithSecrets(
|
||||
executorsAccessToken,
|
||||
authOpenIDClientSecret, authGitLabClientSecret, authGitHubClientSecret,
|
||||
emailSMTPPassword,
|
||||
organizationInvitationsSigningKey,
|
||||
githubClientSecret,
|
||||
dotcomGitHubAppCloudClientSecret,
|
||||
dotcomGitHubAppCloudPrivateKey,
|
||||
),
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
want := getTestSiteWithRedactedSecrets()
|
||||
assert.Equal(t, want, redacted.Site)
|
||||
}
|
||||
|
||||
func TestUnredact(t *testing.T) {
|
||||
func TestUnredactSecrets(t *testing.T) {
|
||||
previousSite := getTestSiteWithSecrets(
|
||||
executorsAccessToken,
|
||||
authOpenIDClientSecret, authGitLabClientSecret, authGitHubClientSecret,
|
||||
emailSMTPPassword,
|
||||
organizationInvitationsSigningKey,
|
||||
githubClientSecret,
|
||||
dotcomGitHubAppCloudClientSecret,
|
||||
dotcomGitHubAppCloudPrivateKey,
|
||||
)
|
||||
|
||||
t.Run("replaces REDACTED with corresponding secret", func(t *testing.T) {
|
||||
input := getTestSiteWithRedactedSecrets()
|
||||
previousSite := getTestSiteWithSecrets(executorsAccessToken, openIDSecret, githubClientSecret, gitlabClientSecret)
|
||||
unredactedSite, err := UnredactSecrets(input, conftypes.RawUnified{Site: previousSite})
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error unredacting secrets: %s", err)
|
||||
}
|
||||
if strings.Contains(unredactedSite, RedactedSecret) {
|
||||
t.Errorf("expected unredacted to contain secrets")
|
||||
}
|
||||
expectedUnredacted := conftypes.RawUnified{Site: previousSite}
|
||||
unredacted := conftypes.RawUnified{Site: unredactedSite}
|
||||
if !unredacted.Equal(expectedUnredacted) {
|
||||
t.Errorf("unexpected output of UnredactSecrets")
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.NotContains(t, unredactedSite, RedactedSecret)
|
||||
assert.Equal(t, previousSite, unredactedSite)
|
||||
})
|
||||
|
||||
t.Run("unredacts secrets AND respects specified edits to secret", func(t *testing.T) {
|
||||
input := getTestSiteWithSecrets("new-access-token", RedactedSecret, "new-github-client-secret", RedactedSecret)
|
||||
previousSite := getTestSiteWithSecrets(executorsAccessToken, openIDSecret, githubClientSecret, gitlabClientSecret)
|
||||
input := getTestSiteWithSecrets(
|
||||
"new"+executorsAccessToken,
|
||||
RedactedSecret, "new"+authGitLabClientSecret, RedactedSecret,
|
||||
RedactedSecret,
|
||||
RedactedSecret,
|
||||
RedactedSecret,
|
||||
RedactedSecret,
|
||||
RedactedSecret,
|
||||
)
|
||||
unredactedSite, err := UnredactSecrets(input, conftypes.RawUnified{Site: previousSite})
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error unredacting secrets: %s", err)
|
||||
}
|
||||
// Expect to have newly-specified secrets and to fill in "REDACTED" secrets w/ secrets from previous site
|
||||
expectedUnredacted := conftypes.RawUnified{Site: getTestSiteWithSecrets("new-access-token", openIDSecret, "new-github-client-secret", gitlabClientSecret)}
|
||||
unredacted := conftypes.RawUnified{Site: unredactedSite}
|
||||
if !unredacted.Equal(expectedUnredacted) {
|
||||
t.Errorf("unexpected output of UnredactSecrets")
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
// Expect to have newly-specified secrets and to fill in "REDACTED" secrets with secrets from previous site
|
||||
want := getTestSiteWithSecrets(
|
||||
"new"+executorsAccessToken,
|
||||
authOpenIDClientSecret, "new"+authGitLabClientSecret, authGitHubClientSecret,
|
||||
emailSMTPPassword,
|
||||
organizationInvitationsSigningKey,
|
||||
githubClientSecret,
|
||||
dotcomGitHubAppCloudClientSecret,
|
||||
dotcomGitHubAppCloudPrivateKey,
|
||||
)
|
||||
assert.Equal(t, want, unredactedSite)
|
||||
})
|
||||
|
||||
t.Run("unredacts secrets and respects edits to config", func(t *testing.T) {
|
||||
newEmail := "new_email@example.com"
|
||||
input := getTestSiteWithSecrets("new-access-token", RedactedSecret, "new-github-client-secret", RedactedSecret, newEmail)
|
||||
previousSite := getTestSiteWithSecrets(executorsAccessToken, openIDSecret, githubClientSecret, gitlabClientSecret)
|
||||
const newEmail = "new_email@example.com"
|
||||
input := getTestSiteWithSecrets(
|
||||
"new"+executorsAccessToken,
|
||||
RedactedSecret, "new"+authGitLabClientSecret, RedactedSecret,
|
||||
RedactedSecret,
|
||||
RedactedSecret,
|
||||
RedactedSecret,
|
||||
RedactedSecret,
|
||||
RedactedSecret,
|
||||
newEmail,
|
||||
)
|
||||
unredactedSite, err := UnredactSecrets(input, conftypes.RawUnified{Site: previousSite})
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error unredacting secrets: %s", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
// Expect new secrets and new email to show up in the unredacted version
|
||||
expectedUnredacted := conftypes.RawUnified{Site: getTestSiteWithSecrets("new-access-token", openIDSecret, "new-github-client-secret", gitlabClientSecret, newEmail)}
|
||||
unredacted := conftypes.RawUnified{Site: unredactedSite}
|
||||
if !unredacted.Equal(expectedUnredacted) {
|
||||
t.Errorf("unexpected output of UnredactSecrets")
|
||||
}
|
||||
want := getTestSiteWithSecrets(
|
||||
"new"+executorsAccessToken,
|
||||
authOpenIDClientSecret, "new"+authGitLabClientSecret, authGitHubClientSecret,
|
||||
emailSMTPPassword,
|
||||
organizationInvitationsSigningKey,
|
||||
githubClientSecret,
|
||||
dotcomGitHubAppCloudClientSecret,
|
||||
dotcomGitHubAppCloudPrivateKey,
|
||||
newEmail,
|
||||
)
|
||||
assert.Equal(t, want, unredactedSite)
|
||||
})
|
||||
}
|
||||
|
||||
func getTestSiteWithRedactedSecrets() string {
|
||||
return getTestSiteWithSecrets(RedactedSecret, RedactedSecret, RedactedSecret, RedactedSecret)
|
||||
return getTestSiteWithSecrets(RedactedSecret, RedactedSecret, RedactedSecret, RedactedSecret, RedactedSecret, RedactedSecret, RedactedSecret, RedactedSecret, RedactedSecret)
|
||||
}
|
||||
|
||||
func getTestSiteWithSecrets(execAccessToken, openIDSecret, githubSecret, gitlabSecret string, optionalEdit ...string) string {
|
||||
func getTestSiteWithSecrets(
|
||||
executorsAccessToken,
|
||||
authOpenIDClientSecret, authGitHubClientSecret, authGitLabClientSecret,
|
||||
emailSMTPPassword,
|
||||
organizationInvitationsSigningKey,
|
||||
githubClientSecret,
|
||||
dotcomGitHubAppCloudClientSecret, dotcomGitHubAppCloudPrivateKey string,
|
||||
optionalEdit ...string,
|
||||
) string {
|
||||
email := "noreply+dev@sourcegraph.com"
|
||||
if len(optionalEdit) > 0 {
|
||||
email = optionalEdit[0]
|
||||
@ -240,8 +289,29 @@ func getTestSiteWithSecrets(execAccessToken, openIDSecret, githubSecret, gitlabS
|
||||
"observability.tracing": {
|
||||
"sampling":"selective"
|
||||
},
|
||||
"externalService.userMode": "all"
|
||||
"externalService.userMode": "all",
|
||||
"email.smtp": {
|
||||
"password": "%s"
|
||||
},
|
||||
"organizationInvitations": {
|
||||
"signingKey": "%s"
|
||||
},
|
||||
"githubClientSecret": "%s",
|
||||
"dotcom": {
|
||||
"githubApp.cloud": {
|
||||
"clientSecret": "%s",
|
||||
"privateKey": "%s"
|
||||
}
|
||||
}
|
||||
}
|
||||
`, email, execAccessToken, openIDSecret, githubSecret, gitlabSecret)
|
||||
`,
|
||||
email,
|
||||
executorsAccessToken,
|
||||
authOpenIDClientSecret, authGitHubClientSecret, authGitLabClientSecret,
|
||||
emailSMTPPassword,
|
||||
organizationInvitationsSigningKey,
|
||||
githubClientSecret,
|
||||
dotcomGitHubAppCloudClientSecret, dotcomGitHubAppCloudPrivateKey,
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user