front_end_builds 0.0.26 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|