secret_hub 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+ [![Gem Version](https://badge.fury.io/rb/secret_hub.svg)](https://badge.fury.io/rb/secret_hub)
5
+ [![Build Status](https://travis-ci.com/DannyBen/secret_hub.svg?branch=master)](https://travis-ci.com/DannyBen/secret_hub)
6
+ [![Maintainability](https://api.codeclimate.com/v1/badges/.../maintainability)](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,16 @@
1
+ require 'mister_bin'
2
+ require 'colsole'
3
+ require 'lp'
4
+
5
+ module SecretHub
6
+ module Commands
7
+ class Base < MisterBin::Command
8
+ include Colsole
9
+
10
+ def github
11
+ @github ||= GitHubClient.new
12
+ end
13
+
14
+ end
15
+ end
16
+ 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
@@ -0,0 +1,3 @@
1
+ module SecretHub
2
+ VERSION = "0.0.1"
3
+ end
data/lib/secret_hub.rb ADDED
@@ -0,0 +1,5 @@
1
+ require 'secret_hub/version'
2
+ require 'secret_hub/exceptions'
3
+ require 'secret_hub/github_client'
4
+
5
+ require 'byebug' if ENV['BYEBUG']
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: []