tenter 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/tenter.rb +75 -1
- data/lib/tenter/helpers.rb +27 -4
- data/lib/tenter/hooks.rb +7 -0
- data/lib/tenter/utils.rb +48 -0
- data/lib/tenter/version.rb +1 -1
- data/tenter.gemspec +17 -15
- metadata +51 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e691f0744f22ab97bd7793c42c570328202deffe78aae13965ca528b1376c2ae
|
4
|
+
data.tar.gz: 1d79b962a619c625d8b812951087d913a3739ff7d8ca0d4e20f4b101ad820059
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8e5516a38863c5885008b5752c86ecd7f5c6ba300b266b4867240c92dfdafb47804cd38ccd6215f2367ec747051bf6c4baca7775f3a8e9550713c5bc1943e587
|
7
|
+
data.tar.gz: 7180c0c1bd2a01091089230f81b506b9d931543bf66ca7708213c06b888ea8ebf6125fab512beef261276b5096e0fcd98c58b5f080bc5d889ce75af805f32f0f
|
data/lib/tenter.rb
CHANGED
@@ -5,7 +5,68 @@ require "tenter/hooks"
|
|
5
5
|
require "tenter/utils"
|
6
6
|
require "tenter/version"
|
7
7
|
|
8
|
+
# A web app that provides webhooks for GitHub
|
9
|
+
#
|
10
|
+
# Tenter is a Sinatra-based web application that provides webhooks for use by
|
11
|
+
# GitHub. It is intended to be used as a gem in a Rack app.
|
12
|
+
#
|
13
|
+
# At its simplest, a user could write a `config.ru` file consisting of:
|
14
|
+
#
|
15
|
+
# require "tenter"
|
16
|
+
#
|
17
|
+
# run Tenter::Hooks
|
18
|
+
#
|
19
|
+
# Tenter comes with a series of sane defaults. A version of Tenter that uses
|
20
|
+
# these will expose endpoints in the form `/run/<action>/in/<dirname>/`. HTTP
|
21
|
+
# POST requests sent to such an endpoint will, if authenticated, result in
|
22
|
+
# Tenter executing the file on your the server's local file system at
|
23
|
+
# `/<doc_root>/<dirname>/commands/<action>`.
|
24
|
+
#
|
25
|
+
# As astute readers will have observed, this is a potentially massive security
|
26
|
+
# vulnerability. I am nevertheless able to sleep at night because Tenter
|
27
|
+
# will only execute a file matching the action in a given directory if:
|
28
|
+
#
|
29
|
+
# 1. the POST request includes an `HTTP_X_HUB_SIGNATURE` header; and
|
30
|
+
# 2. the value of this header matches the result of cryptographically
|
31
|
+
# signing the body of the HTTP request with a key defined by the user.
|
32
|
+
#
|
33
|
+
# By default, the key defined by the user is located in
|
34
|
+
# `/<doc_root>/<dirname>/hooks.yaml`.
|
35
|
+
#
|
36
|
+
# A user can override the default settings for their instance of Tenter by
|
37
|
+
# defining one or more of the following:
|
38
|
+
#
|
39
|
+
# - `:doc_root` (default: `"/var/www"`): The root directory in which each
|
40
|
+
# exposed directory will be located. It's recommended to specify this as an
|
41
|
+
# absolute path.
|
42
|
+
#
|
43
|
+
# - `:config_filename` (default: `"hooks.yaml"`): The filename of the
|
44
|
+
# configuration file in each exposed directory.
|
45
|
+
#
|
46
|
+
# - `:command_dir` (default: `"commands"`): The name of the subdirectory
|
47
|
+
# in which the files to execute are located.
|
48
|
+
#
|
49
|
+
# - `:log_file` (default `"log/commands.log"`): The path to the log file in each
|
50
|
+
# exposed directory where output from your commands will be logged. You can
|
51
|
+
# set this to `nil`to disable logging.
|
52
|
+
#
|
53
|
+
# The easiest way to do that is in the `config.ru` file:
|
54
|
+
#
|
55
|
+
# require "tenter"
|
56
|
+
#
|
57
|
+
# Tenter.settings = { log_file: nil } # disable logging
|
58
|
+
#
|
59
|
+
# run Tenter::Hooks
|
60
|
+
#
|
61
|
+
# @since 0.1.1
|
62
|
+
# @see https://github.com/pyrmont/tenter
|
8
63
|
module Tenter
|
64
|
+
|
65
|
+
# Returns the default settings
|
66
|
+
#
|
67
|
+
# @return [Hash] the default settings
|
68
|
+
#
|
69
|
+
# @since 0.1.1
|
9
70
|
def self.defaults
|
10
71
|
{ doc_root: "/var/www/",
|
11
72
|
config_filename: "hooks.yaml",
|
@@ -14,15 +75,28 @@ module Tenter
|
|
14
75
|
timestamp: true }
|
15
76
|
end
|
16
77
|
|
78
|
+
# Resets Tenter's settings to their defaults
|
79
|
+
#
|
80
|
+
# @since 0.1.1
|
17
81
|
def self.reset
|
18
82
|
@settings = self.defaults
|
19
83
|
end
|
20
84
|
|
85
|
+
# Updates the provided settings
|
86
|
+
#
|
87
|
+
# @param opts [Hash] the keys and values to update
|
88
|
+
#
|
89
|
+
# @since 0.1.1
|
21
90
|
def self.settings=(opts = {})
|
22
91
|
@settings = self.settings.merge opts
|
23
92
|
end
|
24
93
|
|
94
|
+
# Returns the settings for this instance
|
95
|
+
#
|
96
|
+
# @return [Hash] the settings for this instance
|
97
|
+
#
|
98
|
+
# @since 0.1.1
|
25
99
|
def self.settings
|
26
|
-
@settings ||= self.defaults
|
100
|
+
@settings ||= self.defaults
|
27
101
|
end
|
28
102
|
end
|
data/lib/tenter/helpers.rb
CHANGED
@@ -3,14 +3,28 @@
|
|
3
3
|
require "openssl"
|
4
4
|
|
5
5
|
module Tenter
|
6
|
+
|
7
|
+
# Sinatra helpers for Tenter
|
8
|
+
#
|
9
|
+
# These helpers provide idiomatic methods for use in Sinatra routes. This
|
10
|
+
# module is intended to be called by passing it to the `Sinatra::Base.helpers`
|
11
|
+
# method.
|
12
|
+
#
|
13
|
+
# @since 0.1.1
|
14
|
+
# @api private
|
6
15
|
module Helpers
|
16
|
+
|
17
|
+
# Authenticates the request
|
18
|
+
#
|
19
|
+
# @since 0.1.1
|
20
|
+
# @api private
|
7
21
|
def authenticate
|
8
22
|
msg = "X-Hub-Signature header not set"
|
9
23
|
halt 400, msg unless request.env['HTTP_X_HUB_SIGNATURE']
|
10
24
|
|
11
25
|
msg = "X-Hub-Signature header did not match"
|
12
26
|
halt 403, msg unless Tenter::Utils.dir_exists? params[:site_dir]
|
13
|
-
|
27
|
+
|
14
28
|
secret = Tenter::Utils.secret params[:site_dir]
|
15
29
|
|
16
30
|
request_sig = request.env['HTTP_X_HUB_SIGNATURE']
|
@@ -19,11 +33,15 @@ module Tenter
|
|
19
33
|
OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha1'),
|
20
34
|
secret,
|
21
35
|
request_body)
|
22
|
-
|
36
|
+
|
23
37
|
msg = "X-Hub-Signature header did not match"
|
24
38
|
halt 403, msg unless Rack::Utils.secure_compare computed_sig, request_sig
|
25
39
|
end
|
26
40
|
|
41
|
+
# Executes the command
|
42
|
+
#
|
43
|
+
# @since 0.1.1
|
44
|
+
# @api private
|
27
45
|
def initiate
|
28
46
|
command = Tenter::Utils.command params[:command_name], params[:site_dir]
|
29
47
|
|
@@ -34,14 +52,19 @@ module Tenter
|
|
34
52
|
msg = ts + "Initiating: #{command["path"]}\n"
|
35
53
|
Tenter::Utils.append_to_log command["log"], msg
|
36
54
|
|
37
|
-
pid = if defined?(Bundler) && Bundler.respond_to?(:
|
38
|
-
Bundler.
|
55
|
+
pid = if defined?(Bundler) && Bundler.respond_to?(:with_original_env)
|
56
|
+
Bundler.with_original_env { command["proc"].call }
|
39
57
|
else
|
40
58
|
command["proc"].call
|
41
59
|
end
|
42
60
|
(ENV["APP_ENV"] != "test") ? Process.detach(pid) : Process.wait(pid)
|
43
61
|
end
|
44
62
|
|
63
|
+
# Generates the response's HTTP header status and body
|
64
|
+
#
|
65
|
+
# @param message [Symbol] the type of response
|
66
|
+
# @since 0.1.1
|
67
|
+
# @api private
|
45
68
|
def notify(message)
|
46
69
|
case message
|
47
70
|
when :initiated
|
data/lib/tenter/hooks.rb
CHANGED
data/lib/tenter/utils.rb
CHANGED
@@ -3,7 +3,22 @@
|
|
3
3
|
require "yaml"
|
4
4
|
|
5
5
|
module Tenter
|
6
|
+
|
7
|
+
# Utility functions for use by Tenter
|
8
|
+
#
|
9
|
+
# @since 0.1.1
|
10
|
+
# @api private
|
6
11
|
module Utils
|
12
|
+
|
13
|
+
# Loads configuration data from a YAML file for a given directory
|
14
|
+
#
|
15
|
+
# @note The basename of the YAML file is specified in Tenter's configuration
|
16
|
+
# settings.
|
17
|
+
#
|
18
|
+
# @param site_dir [String] the directory containing the YAML file
|
19
|
+
#
|
20
|
+
# @since 0.1.1
|
21
|
+
# @api private
|
7
22
|
def self.config(site_dir)
|
8
23
|
@config ||= {}
|
9
24
|
@config[site_dir] ||=
|
@@ -12,14 +27,40 @@ module Tenter
|
|
12
27
|
Tenter.settings[:config_filename]))
|
13
28
|
end
|
14
29
|
|
30
|
+
# Checks if the directory exists
|
31
|
+
#
|
32
|
+
# @note The directory provided must be given relative to the document root.
|
33
|
+
#
|
34
|
+
# @param site_dir [String] the directory to check
|
35
|
+
#
|
36
|
+
# @since 0.1.1
|
37
|
+
# @api private
|
15
38
|
def self.dir_exists?(site_dir)
|
16
39
|
File.directory? File.join(Tenter.settings[:doc_root], site_dir)
|
17
40
|
end
|
18
41
|
|
42
|
+
# Returns the secret value for a particular directory
|
43
|
+
#
|
44
|
+
# @param site_dir [String} the directory
|
45
|
+
#
|
46
|
+
# @since 0.1.1
|
47
|
+
# @api private
|
19
48
|
def self.secret(site_dir)
|
20
49
|
self.config(site_dir).fetch("secret", nil)
|
21
50
|
end
|
22
51
|
|
52
|
+
# Returns the details of the command to execute
|
53
|
+
#
|
54
|
+
# @note The directory provided must be given relative to the document root.
|
55
|
+
#
|
56
|
+
# @param command_name [String] the name of the file representing the
|
57
|
+
# command
|
58
|
+
# @param site_dir [String} the directory containing the command
|
59
|
+
#
|
60
|
+
# @return [Hash] the details of the command to execute
|
61
|
+
#
|
62
|
+
# @since 0.1.1
|
63
|
+
# @api private
|
23
64
|
def self.command(command_name, site_dir)
|
24
65
|
site_path = File.join(Tenter.settings[:doc_root], site_dir)
|
25
66
|
command = {}
|
@@ -43,6 +84,13 @@ module Tenter
|
|
43
84
|
return command
|
44
85
|
end
|
45
86
|
|
87
|
+
# Appends a statement to the log
|
88
|
+
#
|
89
|
+
# @param log [String] the file representing the log
|
90
|
+
# @param statement [String] the statement to append to the log
|
91
|
+
#
|
92
|
+
# @since 0.1.0
|
93
|
+
# @api private
|
46
94
|
def self.append_to_log(log, statement)
|
47
95
|
File.write(log, statement, mode: "a")
|
48
96
|
end
|
data/lib/tenter/version.rb
CHANGED
data/tenter.gemspec
CHANGED
@@ -3,28 +3,30 @@
|
|
3
3
|
require "./lib/tenter/version"
|
4
4
|
|
5
5
|
Gem::Specification.new do |s|
|
6
|
-
s.name
|
7
|
-
s.version
|
8
|
-
s.authors
|
9
|
-
s.email
|
10
|
-
s.summary
|
6
|
+
s.name = "tenter"
|
7
|
+
s.version = Tenter::VERSION
|
8
|
+
s.authors = ["Michael Camilleri"]
|
9
|
+
s.email = ["mike@inqk.net"]
|
10
|
+
s.summary = "A web app for running user-defined commands"
|
11
11
|
s.description = <<-desc.strip.gsub(/\s+/, " ")
|
12
12
|
Tenter is a Sinatra-based application that provides webhooks for use by
|
13
13
|
GitHub.
|
14
14
|
desc
|
15
|
-
s.homepage
|
16
|
-
s.licenses
|
17
|
-
s.
|
15
|
+
s.homepage = "https://github.com/pyrmont/tenter/"
|
16
|
+
s.licenses = "Unlicense"
|
17
|
+
s.files = Dir["Gemfile", "LICENSE", "tenter.gemspec", "lib/tenter.rb",
|
18
|
+
"lib/**/*.rb"]
|
18
19
|
|
19
|
-
s.
|
20
|
-
|
21
|
-
s.require_paths = ["lib"]
|
22
|
-
|
20
|
+
s.required_ruby_version = ">= 2.5"
|
21
|
+
s.require_paths = ["lib"]
|
23
22
|
s.metadata["allowed_push_host"] = "https://rubygems.org"
|
24
23
|
|
25
24
|
s.add_runtime_dependency "sinatra", "~> 2.0"
|
25
|
+
s.add_runtime_dependency "thin", "~> 1.0"
|
26
26
|
|
27
|
-
s.add_development_dependency "minitest", "~> 5.
|
28
|
-
s.add_development_dependency "rack-test", "~> 1.
|
29
|
-
s.add_development_dependency "rake", "~>
|
27
|
+
s.add_development_dependency "minitest", "~> 5.0"
|
28
|
+
s.add_development_dependency "rack-test", "~> 1.0"
|
29
|
+
s.add_development_dependency "rake", "~> 13.0"
|
30
|
+
s.add_development_dependency "redcarpet", "~> 3.0"
|
31
|
+
s.add_development_dependency "yard", "~> 0.0"
|
30
32
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tenter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Camilleri
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-02-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sinatra
|
@@ -24,48 +24,90 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '2.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: thin
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.0'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: minitest
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
30
44
|
requirements:
|
31
45
|
- - "~>"
|
32
46
|
- !ruby/object:Gem::Version
|
33
|
-
version: '5.
|
47
|
+
version: '5.0'
|
34
48
|
type: :development
|
35
49
|
prerelease: false
|
36
50
|
version_requirements: !ruby/object:Gem::Requirement
|
37
51
|
requirements:
|
38
52
|
- - "~>"
|
39
53
|
- !ruby/object:Gem::Version
|
40
|
-
version: '5.
|
54
|
+
version: '5.0'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: rack-test
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
44
58
|
requirements:
|
45
59
|
- - "~>"
|
46
60
|
- !ruby/object:Gem::Version
|
47
|
-
version: '1.
|
61
|
+
version: '1.0'
|
48
62
|
type: :development
|
49
63
|
prerelease: false
|
50
64
|
version_requirements: !ruby/object:Gem::Requirement
|
51
65
|
requirements:
|
52
66
|
- - "~>"
|
53
67
|
- !ruby/object:Gem::Version
|
54
|
-
version: '1.
|
68
|
+
version: '1.0'
|
55
69
|
- !ruby/object:Gem::Dependency
|
56
70
|
name: rake
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
58
72
|
requirements:
|
59
73
|
- - "~>"
|
60
74
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
75
|
+
version: '13.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '13.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: redcarpet
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '3.0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '3.0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: yard
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0.0'
|
62
104
|
type: :development
|
63
105
|
prerelease: false
|
64
106
|
version_requirements: !ruby/object:Gem::Requirement
|
65
107
|
requirements:
|
66
108
|
- - "~>"
|
67
109
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
110
|
+
version: '0.0'
|
69
111
|
description: Tenter is a Sinatra-based application that provides webhooks for use
|
70
112
|
by GitHub.
|
71
113
|
email:
|
@@ -102,7 +144,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
102
144
|
- !ruby/object:Gem::Version
|
103
145
|
version: '0'
|
104
146
|
requirements: []
|
105
|
-
rubygems_version: 3.
|
147
|
+
rubygems_version: 3.1.2
|
106
148
|
signing_key:
|
107
149
|
specification_version: 4
|
108
150
|
summary: A web app for running user-defined commands
|