Read webhook secret from file into configuration
Signed-off-by: Steven Kriegler <61625851+justusbunsi@users.noreply.github.com>
This commit is contained in:
parent
6c2bb413cd
commit
9c9b7588ab
|
@ -5,15 +5,14 @@ gitea:
|
||||||
|
|
||||||
# Created access token for the user that shall be used as bot account.
|
# Created access token for the user that shall be used as bot account.
|
||||||
# User needs "Read project" permissions with access to "Pull Requests"
|
# 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
|
# 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.
|
# 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
|
# 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.
|
# exists and no webhookSecret is defined here, the bot will ignore the request, because it cannot be validated.
|
||||||
webhookSecret: {}
|
webhookSecret:
|
||||||
# # either plain text
|
value: ""
|
||||||
# value: <...>
|
|
||||||
# # or path to file containing the plain text secret
|
# # or path to file containing the plain text secret
|
||||||
# file: /path/to/gitea/webhook/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.
|
# Created access token for the user that shall be used as bot account.
|
||||||
# User needs "Browse on project" permissions
|
# 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
|
# 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.
|
# request will be ignored.
|
||||||
# The bot looks for `X-Sonar-Webhook-HMAC-SHA256` header containing the sha256 hmac hash of the plain text secret.
|
# 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
|
# If the header exists and no webhookSecret is defined here, the bot will ignore the request, because it cannot be
|
||||||
# validated.
|
# validated.
|
||||||
webhookSecret: {}
|
webhookSecret:
|
||||||
# # either plain text
|
value: ""
|
||||||
# value: <...>
|
|
||||||
# # or path to file containing the plain text secret
|
# # 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.
|
# List of project keys from inside SonarQube that should be handled. Webhooks containing other projects will be ignored.
|
||||||
projects:
|
projects:
|
||||||
|
|
|
@ -2,6 +2,7 @@ package settings
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
@ -12,15 +13,28 @@ type GiteaRepository struct {
|
||||||
Name string
|
Name string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type WebhookSecret struct {
|
||||||
|
Value string
|
||||||
|
File string
|
||||||
|
}
|
||||||
|
|
||||||
type GiteaConfig struct {
|
type GiteaConfig struct {
|
||||||
Url string
|
Url string
|
||||||
Token string
|
Token string
|
||||||
WebhookSecret string `mapstructure:"webhookSecret"`
|
WebhookSecret WebhookSecret `mapstructure:"webhookSecret"`
|
||||||
Repositories []GiteaRepository
|
Repositories []GiteaRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SonarQubeConfig struct {
|
||||||
|
Url string
|
||||||
|
Token string
|
||||||
|
WebhookSecret WebhookSecret `mapstructure:"webhookSecret"`
|
||||||
|
Projects []string
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
Gitea GiteaConfig
|
Gitea GiteaConfig
|
||||||
|
SonarQube SonarQubeConfig
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -30,6 +44,30 @@ func init() {
|
||||||
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||||
viper.AllowEmptyEnv(true)
|
viper.AllowEmptyEnv(true)
|
||||||
viper.AutomaticEnv()
|
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) {
|
func Load(configPath string) {
|
||||||
|
@ -40,12 +78,9 @@ func Load(configPath string) {
|
||||||
panic(fmt.Errorf("Fatal error while reading config file: %w \n", err))
|
panic(fmt.Errorf("Fatal error while reading config file: %w \n", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
if viper.IsSet("gitea") == false {
|
|
||||||
panic("Gitea not configured")
|
|
||||||
}
|
|
||||||
|
|
||||||
var fullConfig struct {
|
var fullConfig struct {
|
||||||
Gitea GiteaConfig
|
Gitea GiteaConfig
|
||||||
|
SonarQube SonarQubeConfig `mapstructure:"sonarqube"`
|
||||||
}
|
}
|
||||||
|
|
||||||
err = viper.Unmarshal(&fullConfig)
|
err = viper.Unmarshal(&fullConfig)
|
||||||
|
@ -54,4 +89,13 @@ func Load(configPath string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Gitea = fullConfig.Gitea
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,12 +13,25 @@ var defaultConfigInlineSecrets []byte = []byte(
|
||||||
`gitea:
|
`gitea:
|
||||||
url: https://example.com/gitea
|
url: https://example.com/gitea
|
||||||
token: d0fcdeb5eaa99c506831f9eb4e63fc7cc484a565
|
token: d0fcdeb5eaa99c506831f9eb4e63fc7cc484a565
|
||||||
webhookSecret: "haxxor"
|
webhookSecret:
|
||||||
|
value: haxxor-gitea-secret
|
||||||
repositories: []
|
repositories: []
|
||||||
sonarqube:
|
sonarqube:
|
||||||
url: https://example.com/sonarqube
|
url: https://example.com/sonarqube
|
||||||
token: a09eb5785b25bb2cbacf48808a677a0709f02d8e
|
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: []
|
projects: []
|
||||||
`)
|
`)
|
||||||
|
|
||||||
|
@ -43,12 +56,6 @@ func TestLoadWithExistingFile(t *testing.T) {
|
||||||
assert.NotPanics(t, func() { Load(os.TempDir()) }, "Unexpected panic while reading existing file")
|
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) {
|
func TestLoadGiteaStructure(t *testing.T) {
|
||||||
WriteConfigFile(t, defaultConfigInlineSecrets)
|
WriteConfigFile(t, defaultConfigInlineSecrets)
|
||||||
Load(os.TempDir())
|
Load(os.TempDir())
|
||||||
|
@ -56,7 +63,9 @@ func TestLoadGiteaStructure(t *testing.T) {
|
||||||
expected := GiteaConfig{
|
expected := GiteaConfig{
|
||||||
Url: "https://example.com/gitea",
|
Url: "https://example.com/gitea",
|
||||||
Token: "d0fcdeb5eaa99c506831f9eb4e63fc7cc484a565",
|
Token: "d0fcdeb5eaa99c506831f9eb4e63fc7cc484a565",
|
||||||
WebhookSecret: "haxxor",
|
WebhookSecret: WebhookSecret{
|
||||||
|
Value: "haxxor-gitea-secret",
|
||||||
|
},
|
||||||
Repositories: []GiteaRepository{},
|
Repositories: []GiteaRepository{},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,7 +73,7 @@ func TestLoadGiteaStructure(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadGiteaStructureWithEnvInjectedWebhookSecret(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")
|
os.Setenv("PRBOT_GITEA_TOKEN", "injected-token")
|
||||||
WriteConfigFile(t, defaultConfigInlineSecrets)
|
WriteConfigFile(t, defaultConfigInlineSecrets)
|
||||||
Load(os.TempDir())
|
Load(os.TempDir())
|
||||||
|
@ -72,14 +81,76 @@ func TestLoadGiteaStructureWithEnvInjectedWebhookSecret(t *testing.T) {
|
||||||
expected := GiteaConfig{
|
expected := GiteaConfig{
|
||||||
Url: "https://example.com/gitea",
|
Url: "https://example.com/gitea",
|
||||||
Token: "injected-token",
|
Token: "injected-token",
|
||||||
WebhookSecret: "injected-secret",
|
WebhookSecret: WebhookSecret{
|
||||||
|
Value: "injected-secret",
|
||||||
|
},
|
||||||
Repositories: []GiteaRepository{},
|
Repositories: []GiteaRepository{},
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.EqualValues(t, expected, Gitea)
|
assert.EqualValues(t, expected, Gitea)
|
||||||
|
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
os.Unsetenv("PRBOT_GITEA_WEBHOOKSECRET")
|
os.Unsetenv("PRBOT_GITEA_WEBHOOKSECRET_VALUE")
|
||||||
os.Unsetenv("PRBOT_GITEA_TOKEN")
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue