site config: redact more secrets (#32436)

This commit is contained in:
Joe Chen 2022-03-14 14:12:51 +08:00 committed by GitHub
parent cb20941505
commit 84b896840d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 201 additions and 89 deletions

View File

@ -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
}

View File

@ -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,
)
}