secret_hub 0.0.1
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 +7 -0
- data/README.md +24 -0
- data/bin/secrethub +16 -0
- data/lib/secret_hub/cli.rb +23 -0
- data/lib/secret_hub/commands/base.rb +16 -0
- data/lib/secret_hub/commands/bulk.rb +105 -0
- data/lib/secret_hub/commands/delete.rb +23 -0
- data/lib/secret_hub/commands/list.rb +22 -0
- data/lib/secret_hub/commands/save.rb +25 -0
- data/lib/secret_hub/exceptions.rb +14 -0
- data/lib/secret_hub/github_client.rb +83 -0
- data/lib/secret_hub/sodium.rb +16 -0
- data/lib/secret_hub/version.rb +3 -0
- data/lib/secret_hub.rb +5 -0
- metadata +126 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 6835039458d739b136ad9d56baf090fe8261f4969d0ba144b7f361a436ca7116
|
4
|
+
data.tar.gz: b404dff3cb2248e124b12b3a5a7a9547a6a88554ae2e043a99b8608d09e850a0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e78bf842dcaa9455a7bf612b49ced104346c8ccabf6de097add9073a14e9d6bb2095d8b3529ab1a1da7e39b4b82da1d2e3d7f4031f0e64decb8268e1d9a09ffa
|
7
|
+
data.tar.gz: 2dc1e103428e7a226a07a893166aac0922757b2f3224a0962c9f7a1bd2a80f4032a919adcbc62c9dabbcef5c5fd31ed80f77c25349b45d77062b4a7d04b57856
|
data/README.md
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
SecretHub
|
2
|
+
==================================================
|
3
|
+
|
4
|
+
[](https://badge.fury.io/rb/secret_hub)
|
5
|
+
[](https://travis-ci.com/DannyBen/secret_hub)
|
6
|
+
[](https://codeclimate.com/github/DannyBen/secret_hub/maintainability)
|
7
|
+
|
8
|
+
---
|
9
|
+
|
10
|
+
Manage GitHub secrets over multiple repositories
|
11
|
+
|
12
|
+
---
|
13
|
+
|
14
|
+
Installation
|
15
|
+
--------------------------------------------------
|
16
|
+
|
17
|
+
$ gem install secret_hub
|
18
|
+
|
19
|
+
|
20
|
+
|
21
|
+
Usage
|
22
|
+
--------------------------------------------------
|
23
|
+
|
24
|
+
TODO
|
data/bin/secrethub
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'secret_hub'
|
3
|
+
require 'secret_hub/cli'
|
4
|
+
require 'colsole'
|
5
|
+
include Colsole
|
6
|
+
|
7
|
+
router = SecretHub::CLI.router
|
8
|
+
|
9
|
+
begin
|
10
|
+
exit router.run ARGV
|
11
|
+
rescue => e
|
12
|
+
puts e.backtrace.reverse if ENV['DEBUG']
|
13
|
+
say! "!txtred!#{e.class}"
|
14
|
+
say! e.message
|
15
|
+
exit 1
|
16
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'mister_bin'
|
2
|
+
require 'secret_hub/commands/base'
|
3
|
+
require 'secret_hub/commands/list'
|
4
|
+
require 'secret_hub/commands/save'
|
5
|
+
require 'secret_hub/commands/delete'
|
6
|
+
require 'secret_hub/commands/bulk'
|
7
|
+
|
8
|
+
module SecretHub
|
9
|
+
class CLI
|
10
|
+
def self.router
|
11
|
+
router = MisterBin::Runner.new version: VERSION,
|
12
|
+
header: "GitHub Secret Manager"
|
13
|
+
|
14
|
+
router.route 'list', to: Commands::List
|
15
|
+
router.route 'save', to: Commands::Save
|
16
|
+
router.route 'delete', to: Commands::Delete
|
17
|
+
router.route 'bulk', to: Commands::Bulk
|
18
|
+
|
19
|
+
router
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module SecretHub
|
4
|
+
module Commands
|
5
|
+
class Bulk < Base
|
6
|
+
summary "Update or delete multiple secrets from multiple repositories"
|
7
|
+
|
8
|
+
usage "secrethub bulk init [CONFIG]"
|
9
|
+
usage "secrethub bulk save [CONFIG --clean]"
|
10
|
+
usage "secrethub bulk clean [CONFIG]"
|
11
|
+
usage "secrethub bulk list [CONFIG]"
|
12
|
+
usage "secrethub bulk (-h|--help)"
|
13
|
+
|
14
|
+
command "init", "Create a sample configuration file in the current directory"
|
15
|
+
command "save", "Save multiple secrets to multiple repositories"
|
16
|
+
command "clean", "Delete secrets from multiple repositories unless they are specified in the config file"
|
17
|
+
command "list", "Show all secrets in all repositories"
|
18
|
+
|
19
|
+
option "-c, --clean", "Also delete any other secret not defined in the config file"
|
20
|
+
|
21
|
+
param "CONFIG", "Path to the configuration file"
|
22
|
+
|
23
|
+
example "secrethub bulk init"
|
24
|
+
example "secrethub bulk clean"
|
25
|
+
example "secrethub bulk update mysecrets.yml"
|
26
|
+
example "secrethub bulk update --clean"
|
27
|
+
|
28
|
+
def init_command
|
29
|
+
raise SecretHubError, "File #{config_file} already exists" if File.exist? config_file
|
30
|
+
|
31
|
+
content = {
|
32
|
+
"user/repo" => %w[SECRET PASSWORD SECRET_KEY],
|
33
|
+
"user/another-repo" => %w[SECRET SECRET_KEY],
|
34
|
+
}
|
35
|
+
|
36
|
+
File.write config_file, content.to_yaml
|
37
|
+
say "!txtgrn!Saved #{config_file}"
|
38
|
+
end
|
39
|
+
|
40
|
+
def save_command
|
41
|
+
clean = args['--clean']
|
42
|
+
|
43
|
+
config.each do |repo, keys|
|
44
|
+
say "!txtblu!#{repo}"
|
45
|
+
update_repo repo, keys
|
46
|
+
clean_repo repo, keys if clean
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def clean_command
|
51
|
+
config.each do |repo, keys|
|
52
|
+
say "!txtblu!#{repo}"
|
53
|
+
clean_repo repo, keys
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def list_command
|
58
|
+
config.each do |repo, keys|
|
59
|
+
say "!txtblu!#{repo}"
|
60
|
+
github.secrets(repo).each do |secret|
|
61
|
+
say "!txtpur!#{secret}"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def clean_repo(repo, keys)
|
69
|
+
repo_keys = github.secrets repo
|
70
|
+
delete_candidates = repo_keys - keys
|
71
|
+
|
72
|
+
delete_candidates.each do |key|
|
73
|
+
say "delete !txtpur!#{key} "
|
74
|
+
success = github.delete_secret repo, key
|
75
|
+
say "!txtgrn!OK"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def update_repo(repo, keys)
|
80
|
+
keys.each do |key|
|
81
|
+
say "save !txtpur!#{key} "
|
82
|
+
github.put_secret repo, key, secret_value(key)
|
83
|
+
say "!txtgrn!OK"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def secret_value(key)
|
88
|
+
ENV[key] || raise(EnvironmentError, "Please set the #{key} environment variable")
|
89
|
+
end
|
90
|
+
|
91
|
+
def config_file
|
92
|
+
args['CONFIG'] || 'secrethub.yml'
|
93
|
+
end
|
94
|
+
|
95
|
+
def config
|
96
|
+
raise ConfigurationError, "Config file not found #{config_flie}" unless File.exist? config_file
|
97
|
+
result = YAML.load_file config_file
|
98
|
+
result.each do |key, value|
|
99
|
+
raise ConfigurationError, "Invalid repo #{key}" unless key.include? '/'
|
100
|
+
raise ConfigurationError, "Invalid keys for #{key} - must be an array" unless value.is_a? Array
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module SecretHub
|
2
|
+
module Commands
|
3
|
+
class Delete < Base
|
4
|
+
summary "Delete a secret from a repository"
|
5
|
+
|
6
|
+
usage "secrethub delete REPO KEY"
|
7
|
+
usage "secrethub delete (-h|--help)"
|
8
|
+
|
9
|
+
param "REPO", "Full name of the GitHub repository (user/repo)"
|
10
|
+
param "KEY", "The name of the secret"
|
11
|
+
|
12
|
+
example "secrethub delete bob/vault PASSWORD"
|
13
|
+
|
14
|
+
def run
|
15
|
+
repo = args['REPO']
|
16
|
+
key = args['KEY']
|
17
|
+
|
18
|
+
success = github.delete_secret repo, key
|
19
|
+
say "Deleted !txtblu!#{repo} !txtpur!#{key}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module SecretHub
|
2
|
+
module Commands
|
3
|
+
class List < Base
|
4
|
+
summary "Show secrets for a repository"
|
5
|
+
|
6
|
+
usage "secrethub list REPO"
|
7
|
+
usage "secrethub list (-h|--help)"
|
8
|
+
|
9
|
+
param "REPO", "Full name of the GitHub repository (user/repo)"
|
10
|
+
|
11
|
+
example "secrethub list bob/repo-woth-secrets"
|
12
|
+
|
13
|
+
def run
|
14
|
+
repo = args['REPO']
|
15
|
+
say "!txtblu!#{repo}"
|
16
|
+
github.secrets(repo).each do |secret|
|
17
|
+
say "!txtpur!#{secret}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module SecretHub
|
2
|
+
module Commands
|
3
|
+
class Save < Base
|
4
|
+
summary "Create or update a secret in a repository"
|
5
|
+
|
6
|
+
usage "secrethub save REPO KEY VALUE"
|
7
|
+
usage "secrethub save (-h|--help)"
|
8
|
+
|
9
|
+
param "REPO", "Full name of the GitHub repository (user/repo)"
|
10
|
+
param "KEY", "The name of the secret"
|
11
|
+
param "VALUE", "The plain text secret value"
|
12
|
+
|
13
|
+
example "secrethub save bob/vault PASSWORD p4ssw0rd"
|
14
|
+
|
15
|
+
def run
|
16
|
+
repo = args['REPO']
|
17
|
+
key = args['KEY']
|
18
|
+
value = args['VALUE']
|
19
|
+
|
20
|
+
github.put_secret repo, key, value
|
21
|
+
say "Saved !txtblu!#{repo} !txtpur!#{key}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module SecretHub
|
2
|
+
SecretHubError = Class.new StandardError
|
3
|
+
ConfigurationError = Class.new SecretHubError
|
4
|
+
EnvironmentError = Class.new SecretHubError
|
5
|
+
|
6
|
+
class APIError < SecretHubError
|
7
|
+
attr_reader :response
|
8
|
+
|
9
|
+
def initialize(response)
|
10
|
+
@response = response
|
11
|
+
super "[#{response.code}] #{response.body}"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
require 'secret_hub/sodium'
|
3
|
+
|
4
|
+
module SecretHub
|
5
|
+
class GitHubClient
|
6
|
+
include Sodium
|
7
|
+
include HTTParty
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
self.class.base_uri ENV['SECRET_HUB_API_BASE'] || 'https://api.github.com'
|
11
|
+
end
|
12
|
+
|
13
|
+
# GET /repos/:owner/:repo/actions/secrets/public-key
|
14
|
+
def public_key(repo)
|
15
|
+
public_keys[repo] ||= get("/repos/#{repo}/actions/secrets/public-key")
|
16
|
+
end
|
17
|
+
|
18
|
+
# GET /repos/:owner/:repo/actions/secrets
|
19
|
+
def secrets(repo)
|
20
|
+
response = get "/repos/#{repo}/actions/secrets"
|
21
|
+
response['secrets'].map { |s| s['name'] }
|
22
|
+
end
|
23
|
+
|
24
|
+
# PUT /repos/:owner/:repo/actions/secrets/:name
|
25
|
+
def put_secret(repo, name, value)
|
26
|
+
secret = encrypt_for_repo repo, value
|
27
|
+
key_id = public_key(repo)['key_id']
|
28
|
+
put "/repos/#{repo}/actions/secrets/#{name}",
|
29
|
+
encrypted_value: secret,
|
30
|
+
key_id: key_id
|
31
|
+
end
|
32
|
+
|
33
|
+
# DELETE /repos/:owner/:repo/actions/secrets/:name
|
34
|
+
def delete_secret(repo, name)
|
35
|
+
delete "/repos/#{repo}/actions/secrets/#{name}"
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def public_keys
|
41
|
+
@public_keys ||= {}
|
42
|
+
end
|
43
|
+
|
44
|
+
def encrypt_for_repo(repo, secret)
|
45
|
+
public_key = public_key(repo)['key']
|
46
|
+
encrypt secret, public_key
|
47
|
+
end
|
48
|
+
|
49
|
+
def get(url)
|
50
|
+
response = self.class.get(url, request_options)
|
51
|
+
response.success? or raise APIError, response
|
52
|
+
response.parsed_response
|
53
|
+
end
|
54
|
+
|
55
|
+
def put(url, args = {})
|
56
|
+
options = { body: args.to_json }
|
57
|
+
all_options = request_options.merge options
|
58
|
+
response = self.class.put url, all_options
|
59
|
+
response.success? or raise APIError, response
|
60
|
+
end
|
61
|
+
|
62
|
+
def delete(url)
|
63
|
+
response = self.class.delete url, request_options
|
64
|
+
response.success? or raise APIError, response
|
65
|
+
end
|
66
|
+
|
67
|
+
def request_options
|
68
|
+
{ headers: headers }
|
69
|
+
end
|
70
|
+
|
71
|
+
def headers
|
72
|
+
{
|
73
|
+
"Authorization" => "token #{secret_token}",
|
74
|
+
"User-Agent" => "SecretHub Gem"
|
75
|
+
}
|
76
|
+
end
|
77
|
+
|
78
|
+
def secret_token
|
79
|
+
ENV['GITHUB_ACCESS_TOKEN'] || raise(EnvironmentError, "Please set GITHUB_ACCESS_TOKEN")
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require "base64"
|
2
|
+
require 'rbnacl'
|
3
|
+
|
4
|
+
module SecretHub
|
5
|
+
module Sodium
|
6
|
+
def encrypt(secret, public_key)
|
7
|
+
key = Base64.decode64 public_key
|
8
|
+
public_key = RbNaCl::PublicKey.new key
|
9
|
+
|
10
|
+
box = RbNaCl::Boxes::Sealed.from_public_key public_key
|
11
|
+
encrypted_secret = box.encrypt secret
|
12
|
+
|
13
|
+
Base64.strict_encode64 encrypted_secret
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/secret_hub.rb
ADDED
metadata
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: secret_hub
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Danny Ben Shitrit
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-02-14 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: mister_bin
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.7'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.7'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: colsole
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.7'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.7'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: httparty
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.17'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.17'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: lp
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.2'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0.2'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rbnacl
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '7.1'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '7.1'
|
83
|
+
description: Command line interface for managing GitHub secrets in bulk
|
84
|
+
email: db@dannyben.com
|
85
|
+
executables:
|
86
|
+
- secrethub
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- README.md
|
91
|
+
- bin/secrethub
|
92
|
+
- lib/secret_hub.rb
|
93
|
+
- lib/secret_hub/cli.rb
|
94
|
+
- lib/secret_hub/commands/base.rb
|
95
|
+
- lib/secret_hub/commands/bulk.rb
|
96
|
+
- lib/secret_hub/commands/delete.rb
|
97
|
+
- lib/secret_hub/commands/list.rb
|
98
|
+
- lib/secret_hub/commands/save.rb
|
99
|
+
- lib/secret_hub/exceptions.rb
|
100
|
+
- lib/secret_hub/github_client.rb
|
101
|
+
- lib/secret_hub/sodium.rb
|
102
|
+
- lib/secret_hub/version.rb
|
103
|
+
homepage: https://github.com/dannyben/secret_hub
|
104
|
+
licenses:
|
105
|
+
- MIT
|
106
|
+
metadata: {}
|
107
|
+
post_install_message:
|
108
|
+
rdoc_options: []
|
109
|
+
require_paths:
|
110
|
+
- lib
|
111
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
112
|
+
requirements:
|
113
|
+
- - ">="
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: 2.4.0
|
116
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
117
|
+
requirements:
|
118
|
+
- - ">="
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: '0'
|
121
|
+
requirements: []
|
122
|
+
rubygems_version: 3.0.3
|
123
|
+
signing_key:
|
124
|
+
specification_version: 4
|
125
|
+
summary: Manage GitHub secrets over multiple repositories
|
126
|
+
test_files: []
|