tenter 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|