Read webhook secret from file into configuration

Signed-off-by: Steven Kriegler <61625851+justusbunsi@users.noreply.github.com>
This commit is contained in:
justusbunsi 2021-06-20 19:47:55 +02:00
parent 6c2bb413cd
commit 9c9b7588ab
No known key found for this signature in database
GPG key ID: 990B348ECAC9C7DB
3 changed files with 139 additions and 26 deletions

View file

@ -5,15 +5,14 @@ gitea:
# Created access token for the user that shall be used as bot account.
# User needs "Read project" permissions with access to "Pull Requests"
token: <...>
token: ""
# If the sent webhook has a signature header, the bot validates the request payload. If the value does not match, the
# request will be ignored.
# The bot looks for `X-Gitea-Signature` header containing the sha256 hmac hash of the plain text secret. If the header
# exists and no webhookSecret is defined here, the bot will ignore the request, because it cannot be validated.
webhookSecret: {}
# # either plain text
# value: <...>
webhookSecret:
value: ""
# # or path to file containing the plain text secret
# file: /path/to/gitea/webhook/secret
@ -35,18 +34,17 @@ sonarqube:
# Created access token for the user that shall be used as bot account.
# User needs "Browse on project" permissions
token: <...>
token: ""
# If the sent webhook has a signature header, the bot validates the request payload. If the value does not match, the
# request will be ignored.
# The bot looks for `X-Sonar-Webhook-HMAC-SHA256` header containing the sha256 hmac hash of the plain text secret.
# If the header exists and no webhookSecret is defined here, the bot will ignore the request, because it cannot be
# validated.
webhookSecret: {}
# # either plain text
# value: <...>
webhookSecret:
value: ""
# # or path to file containing the plain text secret
# file: /path/to/gitea/webhook/secret
# file: /path/to/sonarqube/webhook/secret
# List of project keys from inside SonarQube that should be handled. Webhooks containing other projects will be ignored.
projects:

View file

@ -2,6 +2,7 @@ package settings
import (
"fmt"
"io/ioutil"
"strings"
"github.com/spf13/viper"
@ -12,15 +13,28 @@ type GiteaRepository struct {
Name string
}
type WebhookSecret struct {
Value string
File string
}
type GiteaConfig struct {
Url string
Token string
WebhookSecret string `mapstructure:"webhookSecret"`
WebhookSecret WebhookSecret `mapstructure:"webhookSecret"`
Repositories []GiteaRepository
}
type SonarQubeConfig struct {
Url string
Token string
WebhookSecret WebhookSecret `mapstructure:"webhookSecret"`
Projects []string
}
var (
Gitea GiteaConfig
SonarQube SonarQubeConfig
)
func init() {
@ -30,6 +44,30 @@ func init() {
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
viper.AllowEmptyEnv(true)
viper.AutomaticEnv()
ApplyConfigDefaults()
}
func ApplyConfigDefaults() {
viper.SetDefault("gitea.url", "")
viper.SetDefault("gitea.token", "")
viper.SetDefault("gitea.webhookSecret.value", "")
viper.SetDefault("gitea.webhookSecret.file", "")
viper.SetDefault("gitea.repositories", []interface{}{})
viper.SetDefault("sonarqube.url", "")
viper.SetDefault("sonarqube.token", "")
viper.SetDefault("sonarqube.webhookSecret.value", "")
viper.SetDefault("sonarqube.webhookSecret.file", "")
viper.SetDefault("sonarqube.projects", []string{})
}
func ReadSecretFile(file string) string {
content, err := ioutil.ReadFile(file)
if err != nil {
panic(fmt.Errorf("Cannot read '%s' or it is no regular file. %w", file, err))
}
return string(content)
}
func Load(configPath string) {
@ -40,12 +78,9 @@ func Load(configPath string) {
panic(fmt.Errorf("Fatal error while reading config file: %w \n", err))
}
if viper.IsSet("gitea") == false {
panic("Gitea not configured")
}
var fullConfig struct {
Gitea GiteaConfig
SonarQube SonarQubeConfig `mapstructure:"sonarqube"`
}
err = viper.Unmarshal(&fullConfig)
@ -54,4 +89,13 @@ func Load(configPath string) {
}
Gitea = fullConfig.Gitea
SonarQube = fullConfig.SonarQube
if Gitea.WebhookSecret.File != "" {
Gitea.WebhookSecret.Value = ReadSecretFile(Gitea.WebhookSecret.File)
}
if SonarQube.WebhookSecret.File != "" {
SonarQube.WebhookSecret.Value = ReadSecretFile(SonarQube.WebhookSecret.File)
}
}

View file

@ -13,12 +13,25 @@ var defaultConfigInlineSecrets []byte = []byte(
`gitea:
url: https://example.com/gitea
token: d0fcdeb5eaa99c506831f9eb4e63fc7cc484a565
webhookSecret: "haxxor"
webhookSecret:
value: haxxor-gitea-secret
repositories: []
sonarqube:
url: https://example.com/sonarqube
token: a09eb5785b25bb2cbacf48808a677a0709f02d8e
webhookSecret: "haxxor"
webhookSecret:
value: haxxor-sonarqube-secret
projects: []
`)
var incompleteConfig []byte = []byte(
`gitea:
url: https://example.com/gitea
webhookSecret:
value: haxxor-gitea-secret
sonarqube:
url: https://example.com/sonarqube
token: a09eb5785b25bb2cbacf48808a677a0709f02d8e
projects: []
`)
@ -43,12 +56,6 @@ func TestLoadWithExistingFile(t *testing.T) {
assert.NotPanics(t, func() { Load(os.TempDir()) }, "Unexpected panic while reading existing file")
}
func TestLoadWithMissingGiteaStructure(t *testing.T) {
WriteConfigFile(t, []byte(``))
assert.Panics(t, func() { Load(os.TempDir()) }, "No panic when Gitea is not configured")
}
func TestLoadGiteaStructure(t *testing.T) {
WriteConfigFile(t, defaultConfigInlineSecrets)
Load(os.TempDir())
@ -56,7 +63,9 @@ func TestLoadGiteaStructure(t *testing.T) {
expected := GiteaConfig{
Url: "https://example.com/gitea",
Token: "d0fcdeb5eaa99c506831f9eb4e63fc7cc484a565",
WebhookSecret: "haxxor",
WebhookSecret: WebhookSecret{
Value: "haxxor-gitea-secret",
},
Repositories: []GiteaRepository{},
}
@ -64,7 +73,7 @@ func TestLoadGiteaStructure(t *testing.T) {
}
func TestLoadGiteaStructureWithEnvInjectedWebhookSecret(t *testing.T) {
os.Setenv("PRBOT_GITEA_WEBHOOKSECRET", "injected-secret")
os.Setenv("PRBOT_GITEA_WEBHOOKSECRET_VALUE", "injected-secret")
os.Setenv("PRBOT_GITEA_TOKEN", "injected-token")
WriteConfigFile(t, defaultConfigInlineSecrets)
Load(os.TempDir())
@ -72,14 +81,76 @@ func TestLoadGiteaStructureWithEnvInjectedWebhookSecret(t *testing.T) {
expected := GiteaConfig{
Url: "https://example.com/gitea",
Token: "injected-token",
WebhookSecret: "injected-secret",
WebhookSecret: WebhookSecret{
Value: "injected-secret",
},
Repositories: []GiteaRepository{},
}
assert.EqualValues(t, expected, Gitea)
t.Cleanup(func() {
os.Unsetenv("PRBOT_GITEA_WEBHOOKSECRET")
os.Unsetenv("PRBOT_GITEA_WEBHOOKSECRET_VALUE")
os.Unsetenv("PRBOT_GITEA_TOKEN")
})
}
func TestLoadStructureWithResolvedWebhookFileFromEnvInjected(t *testing.T) {
secretFile := path.Join(os.TempDir(), "webhook-secret-sonarqube")
_ = ioutil.WriteFile(secretFile, []byte(`totally-secret`),0444)
os.Setenv("PRBOT_GITEA_WEBHOOKSECRET_FILE", secretFile)
os.Setenv("PRBOT_SONARQUBE_WEBHOOKSECRET_FILE", secretFile)
os.Setenv("PRBOT_GITEA_TOKEN", "injected-token")
WriteConfigFile(t, incompleteConfig)
Load(os.TempDir())
expectedGitea := GiteaConfig{
Url: "https://example.com/gitea",
Token: "injected-token",
WebhookSecret: WebhookSecret{
Value: "totally-secret",
File: secretFile,
},
Repositories: []GiteaRepository{},
}
expectedSonarQube := SonarQubeConfig{
Url: "https://example.com/sonarqube",
Token: "a09eb5785b25bb2cbacf48808a677a0709f02d8e",
WebhookSecret: WebhookSecret{
Value: "totally-secret",
File: secretFile,
},
Projects: []string{},
}
assert.EqualValues(t, expectedGitea, Gitea)
assert.EqualValues(t, expectedSonarQube, SonarQube)
t.Cleanup(func() {
os.Remove(secretFile)
os.Unsetenv("PRBOT_SONARQUBE_WEBHOOKSECRET_FILE")
os.Unsetenv("PRBOT_GITEA_TOKEN")
})
}
func TestReadSecretFileWhenDirectoryProvided(t *testing.T) {
assert.Panics(t, func() { ReadSecretFile(os.TempDir()) }, "No panic while trying to read content from directory")
}
func TestReadSecretFileWhenMissingFileProvided(t *testing.T) {
assert.Panics(t, func() { ReadSecretFile(path.Join(os.TempDir(), "secret-file")) }, "No panic while trying to read missing file")
}
func TestReadSecretFile(t *testing.T) {
secretFile := path.Join(os.TempDir(), "secret-file")
_ = ioutil.WriteFile(secretFile, []byte(`awesome-secret-content`),0444)
assert.Equal(t, "awesome-secret-content", ReadSecretFile(secretFile))
t.Cleanup(func() {
os.Remove(secretFile)
})
}