Skip to content

Commit

Permalink
Merge pull request #464 from venushka/bitbucket-webhook-secret
Browse files Browse the repository at this point in the history
Adding BitBucket Server secret support.
  • Loading branch information
bastelfreak authored Jan 14, 2019
2 parents 6cf9eba + f8fb5af commit 9e95e84
Show file tree
Hide file tree
Showing 7 changed files with 167 additions and 49 deletions.
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,22 @@ class { 'r10k::webhook':
}
```

### BitBucket Server Secret Support
BitBucket webhooks allow the use of a secret value that gets hashed against the payload to pass a
signature in the request X-Hub-Signature header. To support the secret with the webhook do the
following type of configuration.

```puppet
class { 'r10k::webhook::config':
protected => false,
bitbucket_secret => 'THISISTHEBITBUCKETWEBHOOKSECRET',
}
class { 'r10k::webhook':
require => Class['r10k::webhook::config'],
}
```

### Webhook - remove webhook init script and config file.
For use when moving to Code Manager, or other solutions, and the webhook should be removed.
```puppet
Expand Down
1 change: 1 addition & 0 deletions manifests/params.pp
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@
$webhook_protected = true
$webhook_background = true
$webhook_github_secret = undef
$webhook_bitbucket_secret = undef
$webhook_discovery_timeout = 10
$webhook_client_timeout = 120
$webhook_prefix = false # ':repo' | ':user' | ':command' (or true for backwards compatibility) | 'string' | false
Expand Down
92 changes: 47 additions & 45 deletions manifests/webhook/config.pp
Original file line number Diff line number Diff line change
Expand Up @@ -6,51 +6,52 @@
#
# Zack Smith <zack@puppetlabs.com>
class r10k::webhook::config (
$ensure = true,
Variant[String, Hash] $hash = 'UNSET',
$certname = $r10k::params::webhook_certname,
$certpath = $r10k::params::webhook_certpath,
$user = $r10k::params::webhook_user,
$pass = $r10k::params::webhook_pass,
$bind_address = $r10k::params::webhook_bind_address,
$port = $r10k::params::webhook_port,
$access_logfile = $r10k::params::webhook_access_logfile,
$client_cfg = $r10k::params::webhook_client_cfg,
$default_branch = $r10k::params::webhook_default_branch,
$use_mco_ruby = $r10k::params::webhook_use_mco_ruby,
$protected = $r10k::params::webhook_protected,
$github_secret = $r10k::params::webhook_github_secret,
$discovery_timeout = $r10k::params::webhook_discovery_timeout,
$client_timeout = $r10k::params::webhook_client_timeout,
$prefix = $r10k::params::webhook_prefix,
$prefix_command = $r10k::params::webhook_prefix_command,
$server_software = $r10k::params::webhook_server_software,
$enable_ssl = $r10k::params::webhook_enable_ssl,
$use_mcollective = $r10k::params::webhook_use_mcollective,
$r10k_deploy_arguments = $r10k::params::webhook_r10k_deploy_arguments,
$public_key_path = $r10k::params::webhook_public_key_path,
$private_key_path = $r10k::params::webhook_private_key_path,
$yaml_template = $r10k::params::webhook_yaml_template,
$command_prefix = $r10k::params::webhook_r10k_command_prefix,
$repository_events = $r10k::params::webhook_repository_events,
$allow_uppercase = $r10k::params::webhook_allow_uppercase,
$slack_webhook = $r10k::params::webhook_slack_webhook,
$slack_channel = $r10k::params::webhook_slack_channel,
$slack_username = $r10k::params::webhook_slack_username,
$slack_proxy_url = $r10k::params::webhook_slack_proxy_url,
$rocketchat_webhook = $r10k::params::webhook_rocketchat_webhook,
$rocketchat_channel = $r10k::params::webhook_rocketchat_channel,
$rocketchat_username = $r10k::params::webhook_rocketchat_username,
$configfile_owner = $r10k::params::webhook_configfile_owner,
$configfile_group = $r10k::params::webhook_configfile_group,
$configfile_mode = $r10k::params::webhook_configfile_mode,
$configfile = '/etc/webhook.yaml',
$manage_symlink = false,
$configfile_symlink = '/etc/webhook.yaml',
$enable_mutex_lock = $r10k::params::webhook_enable_mutex_lock,
Array $ignore_environments = $r10k::params::webhook_ignore_environments,
Optional[String] $mco_arguments = $r10k::params::webhook_mco_arguments,
Boolean $generate_types = $r10k::params::webhook_generate_types,
$ensure = true,
Variant[String, Hash] $hash = 'UNSET',
$certname = $r10k::params::webhook_certname,
$certpath = $r10k::params::webhook_certpath,
$user = $r10k::params::webhook_user,
$pass = $r10k::params::webhook_pass,
$bind_address = $r10k::params::webhook_bind_address,
$port = $r10k::params::webhook_port,
$access_logfile = $r10k::params::webhook_access_logfile,
$client_cfg = $r10k::params::webhook_client_cfg,
$default_branch = $r10k::params::webhook_default_branch,
$use_mco_ruby = $r10k::params::webhook_use_mco_ruby,
$protected = $r10k::params::webhook_protected,
$github_secret = $r10k::params::webhook_github_secret,
Optional[String[1]] $bitbucket_secret = $r10k::params::webhook_bitbucket_secret,
$discovery_timeout = $r10k::params::webhook_discovery_timeout,
$client_timeout = $r10k::params::webhook_client_timeout,
$prefix = $r10k::params::webhook_prefix,
$prefix_command = $r10k::params::webhook_prefix_command,
$server_software = $r10k::params::webhook_server_software,
$enable_ssl = $r10k::params::webhook_enable_ssl,
$use_mcollective = $r10k::params::webhook_use_mcollective,
$r10k_deploy_arguments = $r10k::params::webhook_r10k_deploy_arguments,
$public_key_path = $r10k::params::webhook_public_key_path,
$private_key_path = $r10k::params::webhook_private_key_path,
$yaml_template = $r10k::params::webhook_yaml_template,
$command_prefix = $r10k::params::webhook_r10k_command_prefix,
$repository_events = $r10k::params::webhook_repository_events,
$allow_uppercase = $r10k::params::webhook_allow_uppercase,
$slack_webhook = $r10k::params::webhook_slack_webhook,
$slack_channel = $r10k::params::webhook_slack_channel,
$slack_username = $r10k::params::webhook_slack_username,
$slack_proxy_url = $r10k::params::webhook_slack_proxy_url,
$rocketchat_webhook = $r10k::params::webhook_rocketchat_webhook,
$rocketchat_channel = $r10k::params::webhook_rocketchat_channel,
$rocketchat_username = $r10k::params::webhook_rocketchat_username,
$configfile_owner = $r10k::params::webhook_configfile_owner,
$configfile_group = $r10k::params::webhook_configfile_group,
$configfile_mode = $r10k::params::webhook_configfile_mode,
$configfile = '/etc/webhook.yaml',
$manage_symlink = false,
$configfile_symlink = '/etc/webhook.yaml',
$enable_mutex_lock = $r10k::params::webhook_enable_mutex_lock,
Array $ignore_environments = $r10k::params::webhook_ignore_environments,
Optional[String] $mco_arguments = $r10k::params::webhook_mco_arguments,
Boolean $generate_types = $r10k::params::webhook_generate_types,
) inherits r10k::params {

if $hash == 'UNSET' {
Expand All @@ -69,6 +70,7 @@
'access_logfile' => $access_logfile,
'protected' => $protected,
'github_secret' => $github_secret,
'bitbucket_secret' => $bitbucket_secret,
'prefix' => $prefix,
'prefix_command' => $prefix_command,
'server_software' => $server_software,
Expand Down
52 changes: 52 additions & 0 deletions spec/acceptance/signature_webhook_bitbucket_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
require 'spec_helper_acceptance'
require 'openssl'

describe 'BitBucket Secret Enabled, System Ruby with No SSL, Not protected, No mcollective' do
context 'default parameters' do
pp = %(
class { 'r10k':
remote => 'git@yourbitbucketserver.com:someuser/puppet.git',
}
class {'r10k::webhook::config':
enable_ssl => false,
protected => false,
use_mcollective => false,
bitbucket_secret => 'secret',
}
class {'r10k::webhook':
require => Class['r10k::webhook::config'],
}
)

it 'applies with no errors' do
apply_manifest(pp, catch_failures: true)
end
it 'is idempotent' do
apply_manifest(pp, catch_changes: true)
end
describe service('webhook') do
it { is_expected.to be_enabled }
it { is_expected.to be_running }
end

context 'supports style BitBucket payloads via module end point with signature in header' do
HMAC_DIGEST = OpenSSL::Digest.new('sha256')
signature = 'sha1=' + OpenSSL::HMAC.hexdigest(HMAC_DIGEST, 'secret', '{ "repository": { "name": "puppetlabs-stdlib" } }')

describe command("/usr/bin/curl -d '{ \"repository\": { \"name\": \"puppetlabs-stdlib\" } }' -H \"Accept: application/json\" \"http://localhost:8088/module\" -H \"X-Hub-Signature: #{signature}\" -k -q") do
its(:stdout) { is_expected.not_to match %r{.*You shall not pass.*} }
its(:exit_status) { is_expected.to eq 0 }
end
end
context 'supports style BitBucket payloads via payload end point with signature in header' do
HMAC_DIGEST = OpenSSL::Digest.new('sha256')
signature = 'sha1=' + OpenSSL::HMAC.hexdigest(HMAC_DIGEST, 'secret', '{ "ref": "refs/heads/production" }')

describe command("/usr/bin/curl -d '{ \"ref\": \"refs/heads/production\" }' -H \"Accept: application/json\" -H \"X-Hub-Signature: #{signature}\" \"http://localhost:8088/payload\" -k -q") do
its(:stdout) { is_expected.not_to match %r{.*You shall not pass.*} }
its(:exit_status) { is_expected.to eq 0 }
end
end
end
end
File renamed without changes.
45 changes: 45 additions & 0 deletions spec/classes/webhook/config_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,51 @@
it { is_expected.to contain_file('webhook.yaml').with_content(content) }
end

context 'FOSS with BitBucket server secret enabled' do
let :params do
{
bitbucket_secret: 'secret'
}
end

it do
is_expected.to contain_file('webhook.yaml').with(
path: '/etc/webhook.yaml',
ensure: 'file',
owner: 'root',
group: 'root',
mode: '0644',
notify: 'Service[webhook]'
)
end
content = '---
access_logfile: "/var/log/webhook/access.log"
allow_uppercase: true
bind_address: "0.0.0.0"
bitbucket_secret: "secret"
client_cfg: "/var/lib/peadmin/.mcollective"
client_timeout: "120"
command_prefix: "umask 0022;"
default_branch: "production"
discovery_timeout: "10"
enable_mutex_lock: false
enable_ssl: true
generate_types: false
ignore_environments: []
pass: "puppet"
port: "8088"
prefix: false
prefix_command: "/bin/echo example"
protected: true
r10k_deploy_arguments: "-pv"
server_software: "WebHook"
use_mco_ruby: false
use_mcollective: true
user: "puppet"
'
it { is_expected.to contain_file('webhook.yaml').with_content(content) }
end

context 'FOSS with slack webhook enabled' do
let :params do
{
Expand Down
10 changes: 6 additions & 4 deletions templates/webhook.bin.erb
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ class Server < Sinatra::Base
return 200 if ignore_event?

decoded = request.body.read
verify_signature(decoded) if $config['github_secret']
verify_signature(decoded, 'sha1', $config['github_secret']) if $config['github_secret']
verify_signature(decoded, 'sha256', $config['bitbucket_secret']) if $config['bitbucket_secret']
data = JSON.parse(decoded, :quirks_mode => true)

if data['repository'].has_key?('full_name')
Expand Down Expand Up @@ -150,7 +151,8 @@ class Server < Sinatra::Base
else
decoded = request.body.read
end
verify_signature(decoded) if $config['github_secret']
verify_signature(decoded, 'sha1', $config['github_secret']) if $config['github_secret']
verify_signature(decoded, 'sha256', $config['bitbucket_secret']) if $config['bitbucket_secret']
data = JSON.parse(decoded, :quirks_mode => true)

# Iterate the data structure to determine what's should be deployed
Expand Down Expand Up @@ -486,8 +488,8 @@ class Server < Sinatra::Base
!!$config['generate_types']
end

def verify_signature(payload_body)
signature = 'sha1=' + OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha1'), $config['github_secret'], payload_body)
def verify_signature(payload_body, algorithm, secret)
signature = algorithm + '=' + OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new(algorithm), secret, payload_body)
throw(:halt, [500, "Signatures didn't match!\n"]) unless Rack::Utils.secure_compare(signature, request.env['HTTP_X_HUB_SIGNATURE'])
end

Expand Down

0 comments on commit 9e95e84

Please sign in to comment.