front_end_builds 0.0.26 → 0.1.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/app/controllers/front_end_builds/application_controller.rb +4 -0
- data/app/controllers/front_end_builds/apps_controller.rb +1 -1
- data/app/controllers/front_end_builds/builds_controller.rb +22 -22
- data/app/controllers/front_end_builds/pubkeys_controller.rb +50 -0
- data/app/models/front_end_builds/app.rb +0 -8
- data/app/models/front_end_builds/build.rb +27 -1
- data/app/models/front_end_builds/pubkey.rb +74 -0
- data/app/views/front_end_builds/admin/index.html.erb +4 -4
- data/config/routes.rb +1 -0
- data/db/migrate/20150124215337_create_front_end_builds_pubkeys.rb +10 -0
- data/db/migrate/20150124221024_add_pubkey_to_build.rb +7 -0
- data/db/migrate/20150224040537_remove_api_key_from_apps.rb +5 -0
- data/lib/front_end_builds/utils/ssh_pubkey_convert.rb +92 -0
- data/lib/front_end_builds/version.rb +1 -1
- data/public/front_end_builds/assets/admin-8ff31b44f8d2b836f2f6a419fb1d4766.js +3 -0
- data/public/front_end_builds/assets/{admin-bd8ae9b4c42b63d1827d52d21829066d.css → admin-e75b37fa41fedcd7e63427d0d395331a.css} +1 -1
- data/public/front_end_builds/assets/ember-data.js-9e9e7479e5e2ebb89c64179e1c4c702c.map +1 -0
- data/public/front_end_builds/assets/vendor-04e7c54f17406375028018e3cb9433af.js +23 -0
- metadata +26 -6
- data/public/front_end_builds/assets/ZeroClipboard.swf +0 -0
- data/public/front_end_builds/assets/admin-c8d319818b689f7119754abfe0c8742d.js +0 -2
- data/public/front_end_builds/assets/vendor-7a09b85604a427672dac82dc0bcac289.js +0 -22
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b76de17ea5a85d7b6e1c9ca8d26e7a0b55d16399
|
4
|
+
data.tar.gz: 1e0730650a1db2545efd83710b9d6471dfab7786
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9068e3c19df8ade4559bec36fd41bdc0d0543c567773142ed9ead4561dfab6c08bd6379110294c84e6294cace0f45921a0b7176ba2779253cd6388966691576f
|
7
|
+
data.tar.gz: e51b645290dd483bd68c2d65291056fa093c7e9188e451b96aa868773d8493330327f31e9a643e28d32679c0868c32a4cc5f1a76067e84f12af7ca69ad702700
|
@@ -2,7 +2,7 @@ require_dependency "front_end_builds/application_controller"
|
|
2
2
|
|
3
3
|
module FrontEndBuilds
|
4
4
|
class BuildsController < ApplicationController
|
5
|
-
before_filter :set_app!, only: :create
|
5
|
+
before_filter :set_app!, only: [:create]
|
6
6
|
|
7
7
|
def index
|
8
8
|
builds = FrontEndBuilds::Build.where(app_id: params[:app_id])
|
@@ -14,12 +14,13 @@ module FrontEndBuilds
|
|
14
14
|
def create
|
15
15
|
build = @app.builds.new(use_params(:build_create_params))
|
16
16
|
|
17
|
-
if build.save
|
18
|
-
build.
|
19
|
-
build.activate! if build.automatic_activation? and build.master?
|
17
|
+
if build.verify && build.save
|
18
|
+
build.setup!
|
20
19
|
head :ok
|
21
20
|
|
22
21
|
else
|
22
|
+
build.errors[:base] << 'No access - invalid SSH key' if !build.verify
|
23
|
+
|
23
24
|
render(
|
24
25
|
text: 'Could not create the build: ' + build.errors.full_messages.to_s,
|
25
26
|
status: :unprocessable_entity
|
@@ -37,38 +38,37 @@ module FrontEndBuilds
|
|
37
38
|
private
|
38
39
|
|
39
40
|
def set_app!
|
40
|
-
@app =
|
41
|
+
@app = FrontEndBuilds::App
|
42
|
+
.where(name: params[:app_name])
|
43
|
+
.limit(1)
|
44
|
+
.first
|
45
|
+
|
41
46
|
if @app.nil?
|
42
47
|
render(
|
43
|
-
text:
|
48
|
+
text: "No app named #{params[:app_name]}.",
|
44
49
|
status: :unprocessable_entity
|
45
50
|
)
|
51
|
+
|
52
|
+
return false
|
46
53
|
end
|
47
54
|
end
|
48
55
|
|
49
|
-
def
|
50
|
-
|
56
|
+
def _create_params
|
57
|
+
[
|
51
58
|
:branch,
|
52
59
|
:sha,
|
53
60
|
:job,
|
54
|
-
:endpoint
|
55
|
-
|
61
|
+
:endpoint,
|
62
|
+
:signature
|
63
|
+
]
|
56
64
|
end
|
57
65
|
|
58
|
-
def
|
59
|
-
params.
|
60
|
-
:branch,
|
61
|
-
:sha,
|
62
|
-
:job,
|
63
|
-
:endpoint
|
64
|
-
)
|
66
|
+
def build_create_params_rails_3
|
67
|
+
params.slice(*_create_params)
|
65
68
|
end
|
66
69
|
|
67
|
-
def
|
68
|
-
|
69
|
-
name: params[:app_name],
|
70
|
-
api_key: params[:api_key]
|
71
|
-
).limit(1).first
|
70
|
+
def build_create_params_rails_4
|
71
|
+
params.permit(*_create_params)
|
72
72
|
end
|
73
73
|
end
|
74
74
|
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require_dependency "front_end_builds/application_controller"
|
2
|
+
|
3
|
+
module FrontEndBuilds
|
4
|
+
class PubkeysController < ApplicationController
|
5
|
+
def index
|
6
|
+
keys = FrontEndBuilds::Pubkey.order(:name)
|
7
|
+
respond_with_json(pubkeys: keys.map(&:serialize))
|
8
|
+
end
|
9
|
+
|
10
|
+
def create
|
11
|
+
pubkey = FrontEndBuilds::Pubkey
|
12
|
+
.new( use_params(:pubkey_create_params) )
|
13
|
+
|
14
|
+
if pubkey.save
|
15
|
+
respond_with_json(
|
16
|
+
{ pubkey: pubkey.serialize },
|
17
|
+
location: nil
|
18
|
+
)
|
19
|
+
else
|
20
|
+
error!(pubkey.errors)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def destroy
|
25
|
+
pubkey = FrontEndBuilds::Pubkey.find(params[:id])
|
26
|
+
|
27
|
+
if pubkey.destroy
|
28
|
+
respond_with_json(
|
29
|
+
{ pubkey: { id: pubkey.id } },
|
30
|
+
location: nil
|
31
|
+
)
|
32
|
+
else
|
33
|
+
error!(pubkey.errors)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def pubkey_create_params_rails_3
|
40
|
+
params[:pubkey].slice(:name, :pubkey)
|
41
|
+
end
|
42
|
+
|
43
|
+
def pubkey_create_params_rails_4
|
44
|
+
params.require(:pubkey).permit(
|
45
|
+
:name,
|
46
|
+
:pubkey
|
47
|
+
)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -22,9 +22,6 @@ module FrontEndBuilds
|
|
22
22
|
end
|
23
23
|
|
24
24
|
validates :name, presence: true
|
25
|
-
validates :api_key, presence: true
|
26
|
-
|
27
|
-
before_validation :ensure_api_key!
|
28
25
|
|
29
26
|
def self.register_url(name, url)
|
30
27
|
@_url ||= {}
|
@@ -40,15 +37,10 @@ module FrontEndBuilds
|
|
40
37
|
self.class.get_url(name)
|
41
38
|
end
|
42
39
|
|
43
|
-
def ensure_api_key!
|
44
|
-
self.api_key = SecureRandom.uuid if api_key.blank?
|
45
|
-
end
|
46
|
-
|
47
40
|
def serialize
|
48
41
|
{
|
49
42
|
id: id,
|
50
43
|
name: name,
|
51
|
-
api_key: api_key,
|
52
44
|
build_ids: recent_builds.map(&:id),
|
53
45
|
live_build_id: (live_build ? live_build.id : nil),
|
54
46
|
location: get_url,
|
@@ -5,15 +5,18 @@ module FrontEndBuilds
|
|
5
5
|
if defined?(ProtectedAttributes) || ::ActiveRecord::VERSION::MAJOR < 4
|
6
6
|
attr_accessible :branch,
|
7
7
|
:sha,
|
8
|
-
:endpoint
|
8
|
+
:endpoint,
|
9
|
+
:signature
|
9
10
|
end
|
10
11
|
|
11
12
|
belongs_to :app, class_name: "FrontEndBuilds::App"
|
13
|
+
belongs_to :pubkey, class_name: "FrontEndBuilds::Pubkey"
|
12
14
|
|
13
15
|
validates :app, presence: true
|
14
16
|
validates :sha, presence: true
|
15
17
|
validates :branch, presence: true
|
16
18
|
validates :endpoint, presence: true
|
19
|
+
validates :signature, presence: true
|
17
20
|
|
18
21
|
scope :recent, -> { limit(10).order('created_at desc') }
|
19
22
|
|
@@ -62,6 +65,29 @@ module FrontEndBuilds
|
|
62
65
|
.first
|
63
66
|
end
|
64
67
|
|
68
|
+
# Public: Is the signature is valid for the build.
|
69
|
+
#
|
70
|
+
# Returns boolean.
|
71
|
+
def verify
|
72
|
+
!!matching_pubkey
|
73
|
+
end
|
74
|
+
|
75
|
+
# Public: Find the pubkey that can verify the builds
|
76
|
+
# signature.
|
77
|
+
def matching_pubkey
|
78
|
+
Pubkey.all
|
79
|
+
.detect { |key| key.verify(self) }
|
80
|
+
.tap { |key| self.pubkey = key }
|
81
|
+
end
|
82
|
+
|
83
|
+
def setup!
|
84
|
+
fetch!
|
85
|
+
|
86
|
+
if automatic_activation? && master?
|
87
|
+
activate!
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
65
91
|
def live?
|
66
92
|
self == app.live_build
|
67
93
|
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'front_end_builds/utils/ssh_pubkey_convert'
|
2
|
+
require 'base64'
|
3
|
+
require 'openssl'
|
4
|
+
|
5
|
+
module FrontEndBuilds
|
6
|
+
class Pubkey < ActiveRecord::Base
|
7
|
+
if defined?(ProtectedAttributes) || ::ActiveRecord::VERSION::MAJOR < 4
|
8
|
+
attr_accessible :name,
|
9
|
+
:pubkey
|
10
|
+
end
|
11
|
+
|
12
|
+
validates :name, presence: true
|
13
|
+
validates :pubkey, presence: true
|
14
|
+
|
15
|
+
has_many :builds, class_name: "FrontEndBuilds::Build"
|
16
|
+
|
17
|
+
def fingerprint
|
18
|
+
content = pubkey.split(/\s/)[1]
|
19
|
+
|
20
|
+
if content
|
21
|
+
Digest::MD5.hexdigest(Base64.decode64(content))
|
22
|
+
.scan(/.{1,2}/)
|
23
|
+
.join(":")
|
24
|
+
else
|
25
|
+
'Unknown'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def ssh_pubkey?
|
30
|
+
(type, b64, _) = pubkey.split(/\s/)
|
31
|
+
%w{ssh-rsa ssh-dss}.include?(type) && b64.present?
|
32
|
+
end
|
33
|
+
|
34
|
+
# Public: In order to verify a signature we need the key to be an OpenSSL
|
35
|
+
# RSA PKey and not a string that you would find in an ssh pubkey key. Most
|
36
|
+
# people are going to be adding ssh public keys to their build system, this
|
37
|
+
# method will covert them to OpenSSL RSA if needed.
|
38
|
+
def to_rsa_pkey
|
39
|
+
FrontEndBuilds::Utils::SSHPubKeyConvert
|
40
|
+
.convert(pubkey)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Public: Will verify that the sigurate has access to deploy the build
|
44
|
+
# object. The signature includes the endpoint and app name.
|
45
|
+
#
|
46
|
+
# Returns boolean
|
47
|
+
def verify(build)
|
48
|
+
# TODO might as well cache this and store in the db so we dont have to
|
49
|
+
# convert every time
|
50
|
+
pkey = to_rsa_pkey
|
51
|
+
signature = Base64.decode64(build.signature)
|
52
|
+
digest = OpenSSL::Digest::SHA256.new
|
53
|
+
expected = "#{build.app.name}-#{build.endpoint}"
|
54
|
+
|
55
|
+
pkey.verify(digest, signature, expected)
|
56
|
+
end
|
57
|
+
|
58
|
+
def last_build
|
59
|
+
builds
|
60
|
+
.order('created_at desc')
|
61
|
+
.limit(1)
|
62
|
+
.first
|
63
|
+
end
|
64
|
+
|
65
|
+
def serialize
|
66
|
+
{
|
67
|
+
id: id,
|
68
|
+
name: name,
|
69
|
+
fingerprint: fingerprint,
|
70
|
+
lastUsedAt: last_build.try(:created_at)
|
71
|
+
}
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -8,7 +8,7 @@
|
|
8
8
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
9
9
|
|
10
10
|
<base href="/BASEURL/" />
|
11
|
-
<meta name="admin/config/environment" content="%7B%22modulePrefix%22%3A%22admin%22%2C%22environment%22%3A%22production%22%2C%22baseURL%22%3A%22BASEURL%22%2C%22locationType%22%3A%22auto%22%2C%22usePodsByDefault%22%3Atrue%2C%22podModulePrefix%22%3A%22admin/pods%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%7D%2C%22APP%22%3A%7B%7D%2C%
|
11
|
+
<meta name="admin/config/environment" content="%7B%22modulePrefix%22%3A%22admin%22%2C%22environment%22%3A%22production%22%2C%22baseURL%22%3A%22BASEURL%22%2C%22locationType%22%3A%22auto%22%2C%22usePodsByDefault%22%3Atrue%2C%22podModulePrefix%22%3A%22admin/pods%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%7D%2C%22APP%22%3A%7B%7D%2C%22contentSecurityPolicyHeader%22%3A%22Content-Security-Policy-Report-Only%22%2C%22contentSecurityPolicy%22%3A%7B%22default-src%22%3A%22%27none%27%22%2C%22script-src%22%3A%22%27self%27%22%2C%22font-src%22%3A%22%27self%27%22%2C%22connect-src%22%3A%22%27self%27%22%2C%22img-src%22%3A%22%27self%27%22%2C%22style-src%22%3A%22%27self%27%22%2C%22media-src%22%3A%22%27self%27%22%7D%2C%22ember-pretenderify%22%3A%7B%22usingProxy%22%3Afalse%7D%2C%22exportApplicationGlobal%22%3Afalse%7D" />
|
12
12
|
|
13
13
|
<script>
|
14
14
|
window.RAILS_ENV = {
|
@@ -16,15 +16,15 @@
|
|
16
16
|
}
|
17
17
|
</script>
|
18
18
|
<link rel="stylesheet" href="/front_end_builds/assets/vendor-a8105f0efcfa2b3ff559e3a06333c43e.css">
|
19
|
-
<link rel="stylesheet" href="/front_end_builds/assets/admin-
|
19
|
+
<link rel="stylesheet" href="/front_end_builds/assets/admin-e75b37fa41fedcd7e63427d0d395331a.css">
|
20
20
|
|
21
21
|
|
22
22
|
</head>
|
23
23
|
<body>
|
24
24
|
|
25
25
|
|
26
|
-
<script src="/front_end_builds/assets/vendor-
|
27
|
-
<script src="/front_end_builds/assets/admin-
|
26
|
+
<script src="/front_end_builds/assets/vendor-04e7c54f17406375028018e3cb9433af.js"></script>
|
27
|
+
<script src="/front_end_builds/assets/admin-8ff31b44f8d2b836f2f6a419fb1d4766.js"></script>
|
28
28
|
|
29
29
|
|
30
30
|
</body>
|
data/config/routes.rb
CHANGED
@@ -3,6 +3,7 @@ FrontEndBuilds::Engine.routes.draw do
|
|
3
3
|
scope :api, path: '/api' do
|
4
4
|
resources :apps, only: [:index, :show, :create, :update, :destroy]
|
5
5
|
resources :builds, only: [:index, :show]
|
6
|
+
resources :pubkeys, only: [:index, :create, :destroy]
|
6
7
|
resources :host_apps, only: [:show]
|
7
8
|
end
|
8
9
|
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# This is needed to convert an SSH pubkey into a RSA OpenSSL pubkey that we can
|
2
|
+
# use to verify the signature. I got this from:
|
3
|
+
#
|
4
|
+
# https://github.com/mytestbed/omf/blob/master/omf_common/lib/omf_common/auth/ssh_pub_key_convert.rb
|
5
|
+
#
|
6
|
+
|
7
|
+
module FrontEndBuilds::Utils
|
8
|
+
# Copyright (c) 2012 National ICT Australia Limited (NICTA).
|
9
|
+
# This software may be used and distributed solely under the terms of the MIT license (License).
|
10
|
+
# You should find a copy of the License in LICENSE.TXT or at http://opensource.org/licenses/MIT.
|
11
|
+
# By downloading or using this software you accept the terms and the liability disclaimer in the License.
|
12
|
+
|
13
|
+
require 'base64'
|
14
|
+
require 'openssl'
|
15
|
+
|
16
|
+
# This file provides a converter that accepts an SSH public key string
|
17
|
+
# and converts it to an OpenSSL::PKey::RSA object for use in verifying
|
18
|
+
# received messages. (DSA support pending).
|
19
|
+
#
|
20
|
+
class SSHPubKeyConvert
|
21
|
+
# Unpack a 4-byte unsigned integer from the +bytes+ array.
|
22
|
+
#
|
23
|
+
# Returns a pair (+u32+, +bytes+), where +u32+ is the extracted
|
24
|
+
# unsigned integer, and +bytes+ is the remainder of the original
|
25
|
+
# +bytes+ array that follows +u32+.
|
26
|
+
#
|
27
|
+
def self.unpack_u32(bytes)
|
28
|
+
return bytes.unpack("N")[0], bytes[4..-1]
|
29
|
+
end
|
30
|
+
|
31
|
+
# Unpack a string from the +bytes+ array. Exactly +len+ bytes will
|
32
|
+
# be extracted.
|
33
|
+
#
|
34
|
+
# Returns a pair (+string+, +bytes+), where +string+ is the
|
35
|
+
# extracted string (of length +len+), and +bytes+ is the remainder
|
36
|
+
# of the original +bytes+ array that follows +string+.
|
37
|
+
#
|
38
|
+
def self.unpack_string(bytes, len)
|
39
|
+
return bytes.unpack("A#{len}")[0], bytes[len..-1]
|
40
|
+
end
|
41
|
+
|
42
|
+
# Convert a string in SSH public key format to a key object
|
43
|
+
# suitable for use with OpenSSL. If the key is an RSA key then an
|
44
|
+
# OpenSSL::PKey::RSA object is returned. If the key is a DSA key
|
45
|
+
# then an OpenSSL::PKey::DSA object is returned. In either case,
|
46
|
+
# the object returned is suitable for encrypting data or verifying
|
47
|
+
# signatures, but cannot be used for decrypting or signing.
|
48
|
+
#
|
49
|
+
# The +keystring+ should be a single line, as per an SSH public key
|
50
|
+
# file as generated by +ssh-keygen+, or a line from an SSH
|
51
|
+
# +authorized_keys+ file.
|
52
|
+
#
|
53
|
+
def self.convert(keystring)
|
54
|
+
(_, b64, _) = keystring.split(' ')
|
55
|
+
raise ArgumentError, "Invalid SSH public key '#{keystring}'" if b64.nil?
|
56
|
+
|
57
|
+
decoded_key = Base64.decode64(b64)
|
58
|
+
(n, bytes) = unpack_u32(decoded_key)
|
59
|
+
(keytype, bytes) = unpack_string(bytes, n)
|
60
|
+
|
61
|
+
if keytype == "ssh-rsa"
|
62
|
+
(n, bytes) = unpack_u32(bytes)
|
63
|
+
(estr, bytes) = unpack_string(bytes, n)
|
64
|
+
(n, bytes) = unpack_u32(bytes)
|
65
|
+
(nstr, bytes) = unpack_string(bytes, n)
|
66
|
+
|
67
|
+
key = OpenSSL::PKey::RSA.new
|
68
|
+
key.n = OpenSSL::BN.new(nstr, 2)
|
69
|
+
key.e = OpenSSL::BN.new(estr, 2)
|
70
|
+
key
|
71
|
+
elsif keytype == 'ssh-dss'
|
72
|
+
(n, bytes) = unpack_u32(bytes)
|
73
|
+
(pstr, bytes) = unpack_string(bytes, n)
|
74
|
+
(n, bytes) = unpack_u32(bytes)
|
75
|
+
(qstr, bytes) = unpack_string(bytes, n)
|
76
|
+
(n, bytes) = unpack_u32(bytes)
|
77
|
+
(gstr, bytes) = unpack_string(bytes, n)
|
78
|
+
(n, bytes) = unpack_u32(bytes)
|
79
|
+
(pkstr, bytes) = unpack_string(bytes, n)
|
80
|
+
|
81
|
+
key = OpenSSL::PKey::DSA.new
|
82
|
+
key.p = OpenSSL::BN.new(pstr, 2)
|
83
|
+
key.q = OpenSSL::BN.new(qstr, 2)
|
84
|
+
key.g = OpenSSL::BN.new(gstr, 2)
|
85
|
+
key.pub_key = OpenSSL::BN.new(pkstr, 2)
|
86
|
+
key
|
87
|
+
else
|
88
|
+
nil
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|