safedb 0.01.0001
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/.gitignore +8 -0
- data/.yardopts +3 -0
- data/Gemfile +10 -0
- data/LICENSE +21 -0
- data/README.md +793 -0
- data/Rakefile +16 -0
- data/bin/safe +5 -0
- data/lib/configs/README.md +58 -0
- data/lib/extension/array.rb +162 -0
- data/lib/extension/dir.rb +35 -0
- data/lib/extension/file.rb +123 -0
- data/lib/extension/hash.rb +33 -0
- data/lib/extension/string.rb +572 -0
- data/lib/factbase/facts.safedb.net.ini +38 -0
- data/lib/interprete.rb +462 -0
- data/lib/keytools/PRODUCE_RAND_SEQ_USING_DEV_URANDOM.txt +0 -0
- data/lib/keytools/kdf.api.rb +243 -0
- data/lib/keytools/kdf.bcrypt.rb +265 -0
- data/lib/keytools/kdf.pbkdf2.rb +262 -0
- data/lib/keytools/kdf.scrypt.rb +190 -0
- data/lib/keytools/key.64.rb +326 -0
- data/lib/keytools/key.algo.rb +109 -0
- data/lib/keytools/key.api.rb +1391 -0
- data/lib/keytools/key.db.rb +330 -0
- data/lib/keytools/key.docs.rb +195 -0
- data/lib/keytools/key.error.rb +110 -0
- data/lib/keytools/key.id.rb +271 -0
- data/lib/keytools/key.ident.rb +243 -0
- data/lib/keytools/key.iv.rb +107 -0
- data/lib/keytools/key.local.rb +259 -0
- data/lib/keytools/key.now.rb +402 -0
- data/lib/keytools/key.pair.rb +259 -0
- data/lib/keytools/key.pass.rb +120 -0
- data/lib/keytools/key.rb +585 -0
- data/lib/logging/gem.logging.rb +132 -0
- data/lib/modules/README.md +43 -0
- data/lib/modules/cryptology/aes-256.rb +154 -0
- data/lib/modules/cryptology/amalgam.rb +70 -0
- data/lib/modules/cryptology/blowfish.rb +130 -0
- data/lib/modules/cryptology/cipher.rb +207 -0
- data/lib/modules/cryptology/collect.rb +138 -0
- data/lib/modules/cryptology/crypt.io.rb +225 -0
- data/lib/modules/cryptology/engineer.rb +99 -0
- data/lib/modules/mappers/dictionary.rb +288 -0
- data/lib/modules/storage/coldstore.rb +186 -0
- data/lib/modules/storage/git.store.rb +399 -0
- data/lib/session/fact.finder.rb +334 -0
- data/lib/session/require.gem.rb +112 -0
- data/lib/session/time.stamp.rb +340 -0
- data/lib/session/user.home.rb +49 -0
- data/lib/usecase/cmd.rb +487 -0
- data/lib/usecase/config/README.md +57 -0
- data/lib/usecase/docker/README.md +146 -0
- data/lib/usecase/docker/docker.rb +49 -0
- data/lib/usecase/edit/README.md +43 -0
- data/lib/usecase/edit/delete.rb +46 -0
- data/lib/usecase/export.rb +40 -0
- data/lib/usecase/files/README.md +37 -0
- data/lib/usecase/files/eject.rb +56 -0
- data/lib/usecase/files/file_me.rb +78 -0
- data/lib/usecase/files/read.rb +169 -0
- data/lib/usecase/files/write.rb +89 -0
- data/lib/usecase/goto.rb +57 -0
- data/lib/usecase/id.rb +36 -0
- data/lib/usecase/import.rb +157 -0
- data/lib/usecase/init.rb +63 -0
- data/lib/usecase/jenkins/README.md +146 -0
- data/lib/usecase/jenkins/jenkins.rb +208 -0
- data/lib/usecase/login.rb +71 -0
- data/lib/usecase/logout.rb +28 -0
- data/lib/usecase/open.rb +71 -0
- data/lib/usecase/print.rb +40 -0
- data/lib/usecase/put.rb +81 -0
- data/lib/usecase/set.rb +44 -0
- data/lib/usecase/show.rb +138 -0
- data/lib/usecase/terraform/README.md +91 -0
- data/lib/usecase/terraform/terraform.rb +121 -0
- data/lib/usecase/token.rb +35 -0
- data/lib/usecase/update/README.md +55 -0
- data/lib/usecase/update/rename.rb +180 -0
- data/lib/usecase/use.rb +41 -0
- data/lib/usecase/verse.rb +20 -0
- data/lib/usecase/view.rb +71 -0
- data/lib/usecase/vpn/README.md +150 -0
- data/lib/usecase/vpn/vpn.ini +31 -0
- data/lib/usecase/vpn/vpn.rb +54 -0
- data/lib/version.rb +3 -0
- data/safedb.gemspec +34 -0
- metadata +193 -0
data/lib/usecase/id.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
module SafeDb
|
4
|
+
|
5
|
+
|
6
|
+
class Id < UseCase
|
7
|
+
|
8
|
+
|
9
|
+
def execute
|
10
|
+
|
11
|
+
puts ""
|
12
|
+
puts KeyNow.grab()
|
13
|
+
puts KeyNow.fetch()
|
14
|
+
puts ""
|
15
|
+
|
16
|
+
return
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
# Perform pre-conditional validations in preparation to executing the main flow
|
22
|
+
# of events for this use case. This method may throw the below exceptions.
|
23
|
+
#
|
24
|
+
# @raise [SafeDirNotConfigured] if the safe's url has not been configured
|
25
|
+
# @raise [EmailAddrNotConfigured] if the email address has not been configured
|
26
|
+
# @raise [StoreUrlNotConfigured] if the crypt store url is not configured
|
27
|
+
def pre_validation
|
28
|
+
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
module SafeDb
|
4
|
+
|
5
|
+
# The <b>import use case</b> follows <b>open</b> and it pulls a file into an
|
6
|
+
# <em>(encrypted at rest)</em> <b>envelope</b> while writing metadata about
|
7
|
+
# the file into the opened tree dictionary position.
|
8
|
+
#
|
9
|
+
# == import and reimport commands
|
10
|
+
#
|
11
|
+
# - the import command expects a path parameter and errors if not recvd
|
12
|
+
# - the reimport command is happy with either one or zero parameters
|
13
|
+
#
|
14
|
+
# If the reimport command has no parameters it expects that the opened path
|
15
|
+
# already contains an imported file. It uses the import.path key to locate
|
16
|
+
# the file.
|
17
|
+
#
|
18
|
+
# If the path parameter is given to reimport it uses it and also resets the
|
19
|
+
# import.path key to reflect the path it was given.
|
20
|
+
#
|
21
|
+
# == garbage collect dangling files
|
22
|
+
#
|
23
|
+
# Like dangling envelopes - dangling files will pop up when re-imported.
|
24
|
+
# These are handled by the garbage collection policy which can be to
|
25
|
+
# remove immediately - remove on next login - remove after a time period
|
26
|
+
# or to never remove (manual garbage collection).
|
27
|
+
#
|
28
|
+
class Import < UseCase
|
29
|
+
|
30
|
+
attr_writer :secret_id, :secret_value
|
31
|
+
|
32
|
+
# The <b>put use case</b> follows <b>open</b> and it adds secrets into an
|
33
|
+
# <em>(encrypted at rest)</em> envelope. Put can be called many times to
|
34
|
+
# add secrets. Finally the <b>lock use case</b> commits all opened secrets
|
35
|
+
# into the configured storage engines.
|
36
|
+
#
|
37
|
+
# Calling <em>put</em> <b>before</b> calling open or <b>after</b> calling lock
|
38
|
+
# is not allowed and will result in an error.
|
39
|
+
#
|
40
|
+
# == Put Pre-Conditions
|
41
|
+
#
|
42
|
+
# When the put use case is called - the below conditions ring true.
|
43
|
+
#
|
44
|
+
# - the <b>folder path</b> ending in ../../my must exist
|
45
|
+
# - a session id, filename and encryption key ( in workstation config )
|
46
|
+
#
|
47
|
+
# == Observable Value
|
48
|
+
#
|
49
|
+
# The observable value delivered by +put+ boils down to
|
50
|
+
#
|
51
|
+
# - a new <b>friends.xyz123abc.os.txt</b> file if this is the first put.
|
52
|
+
# - a new group_name/key_name (like monica/surname) entry is added if required
|
53
|
+
# - a secret value is added against the key or updated if it already exists
|
54
|
+
# - a new session id and encryption key is generated and used to re-encrypt
|
55
|
+
def execute
|
56
|
+
|
57
|
+
return unless ops_key_exists?
|
58
|
+
master_db = KeyApi.read_master_db()
|
59
|
+
|
60
|
+
puts "---\n"
|
61
|
+
puts "--- The Master Database (Before)\n"
|
62
|
+
puts "---\n"
|
63
|
+
puts JSON.pretty_generate( master_db )
|
64
|
+
puts "---\n"
|
65
|
+
|
66
|
+
return if unopened_envelope?( master_db )
|
67
|
+
|
68
|
+
envelope_id = ENVELOPE_KEY_PREFIX + master_db[ ENV_PATH ]
|
69
|
+
has_content = KeyApi.db_envelope_exists?( master_db[ envelope_id ] )
|
70
|
+
|
71
|
+
# --
|
72
|
+
# -- To get hold of the content we must either
|
73
|
+
# --
|
74
|
+
# -- a) unlock it using the breadcrumbs or
|
75
|
+
# -- b) start afresh with a new content db
|
76
|
+
# --
|
77
|
+
content_box = KeyDb.from_json( KeyApi.content_unlock( master_db[ envelope_id ] ) ) if has_content
|
78
|
+
content_box = KeyDb.new() unless has_content
|
79
|
+
content_hdr = create_header()
|
80
|
+
|
81
|
+
# --
|
82
|
+
# -- If no content envelope exists we need to place
|
83
|
+
# -- an empty one inside the appdb content database.
|
84
|
+
# --
|
85
|
+
master_db[ envelope_id ] = {} unless has_content
|
86
|
+
|
87
|
+
# --
|
88
|
+
# -- This is the PUT use case so we append a
|
89
|
+
# --
|
90
|
+
# -- a) key for the new dictionary entry
|
91
|
+
# -- b) value for the new dictionary entry
|
92
|
+
# --
|
93
|
+
# -- into the current content envelope and write
|
94
|
+
# -- the envelope to the content filepath.
|
95
|
+
# --
|
96
|
+
crumbs_dict = master_db[ envelope_id ]
|
97
|
+
content_box.create_entry( master_db[ KEY_PATH ], @secret_id, @secret_value )
|
98
|
+
KeyApi.content_lock( crumbs_dict, content_box.to_json, content_hdr )
|
99
|
+
|
100
|
+
puts "---\n"
|
101
|
+
puts "--- The Master Database (After)\n"
|
102
|
+
puts "---\n"
|
103
|
+
puts JSON.pretty_generate( master_db )
|
104
|
+
puts "---\n"
|
105
|
+
|
106
|
+
# --
|
107
|
+
# -- Three envelope crumbs namely the external ID, the
|
108
|
+
# -- random iv and the crypt key are written afreshinto
|
109
|
+
# -- the master database.
|
110
|
+
# --
|
111
|
+
KeyApi.write_master_db( content_hdr, master_db )
|
112
|
+
print_put_success
|
113
|
+
|
114
|
+
return
|
115
|
+
|
116
|
+
|
117
|
+
# ---> secret_ids = @secret_id.split("/")
|
118
|
+
# ---> if ( envelope.has_key? secret_ids.first )
|
119
|
+
# ---> envelope[secret_ids.first][secret_ids.last] = @secret_value
|
120
|
+
# ---> else
|
121
|
+
# ---> envelope[secret_ids.first] = { secret_ids.last => @secret_value }
|
122
|
+
# ---> end
|
123
|
+
|
124
|
+
end
|
125
|
+
|
126
|
+
|
127
|
+
private
|
128
|
+
|
129
|
+
|
130
|
+
def print_put_success
|
131
|
+
|
132
|
+
puts ""
|
133
|
+
puts "Success putting a key/value pair into the open envelope."
|
134
|
+
puts "You can put more in and then close the envelope."
|
135
|
+
puts ""
|
136
|
+
puts " #{COMMANDMENT} close"
|
137
|
+
puts ""
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
|
142
|
+
# Perform pre-conditional validations in preparation to executing the main flow
|
143
|
+
# of events for this use case. This method may throw the below exceptions.
|
144
|
+
#
|
145
|
+
# @raise [SafeDirNotConfigured] if the safe's url has not been configured
|
146
|
+
# @raise [EmailAddrNotConfigured] if the email address has not been configured
|
147
|
+
# @raise [StoreUrlNotConfigured] if the crypt store url is not configured
|
148
|
+
def pre_validation
|
149
|
+
|
150
|
+
|
151
|
+
end
|
152
|
+
|
153
|
+
|
154
|
+
end
|
155
|
+
|
156
|
+
|
157
|
+
end
|
data/lib/usecase/init.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
module SafeDb
|
4
|
+
|
5
|
+
# The <b>init use case</b> initializes safe thus preparing it
|
6
|
+
# for the ability to lock secrets, unlock them, transport their keys and
|
7
|
+
# much more.
|
8
|
+
#
|
9
|
+
# safe is a <b>(glorified) placeholder</b>. It takes things in now,
|
10
|
+
# keeps them safe and gives them back later, in a <b>helpful manner</b>.
|
11
|
+
#
|
12
|
+
# == Alternat Error Flows
|
13
|
+
#
|
14
|
+
# An error will be thrown
|
15
|
+
#
|
16
|
+
# - if safe cannot create, extend, read or write the drive folder
|
17
|
+
# - if the domain is already in the configuration file
|
18
|
+
# - if domain has non alphanums, excl hyphens, underscores, @ symbols, periods
|
19
|
+
# - if domain does not begin or end with alphanums.
|
20
|
+
# - if non alpha-nums (excl at signs) appear consecutively
|
21
|
+
# - if no alpha-nums appear in the string
|
22
|
+
# - if the domain string's length is less than 5
|
23
|
+
# - if "safedb.net" appears twice (or more) in a directory tree
|
24
|
+
#
|
25
|
+
class Init < UseCase
|
26
|
+
|
27
|
+
attr_writer :master_p4ss, :domain_name, :base_path
|
28
|
+
|
29
|
+
|
30
|
+
# The init use case prepares the <b>safe</b> so that you can <b>open</b> an envelope,
|
31
|
+
# <b>put</b> secrets into it and then <b>seal</b> (lock) it. Locking effectively writes
|
32
|
+
# crypted blocks to both keystore and crypt store.
|
33
|
+
def execute
|
34
|
+
|
35
|
+
return unless ops_key_exists?
|
36
|
+
|
37
|
+
KeyApi.init_app_domain( @domain_name, @base_path )
|
38
|
+
keys_setup = KeyApi.is_domain_keys_setup?( @domain_name )
|
39
|
+
|
40
|
+
if ( keys_setup )
|
41
|
+
print_already_initialized
|
42
|
+
return
|
43
|
+
end
|
44
|
+
|
45
|
+
domain_password = KeyPass.password_from_shell( true )
|
46
|
+
KeyApi.setup_domain_keys( @domain_name, domain_password, create_header() )
|
47
|
+
print_domain_initialized
|
48
|
+
|
49
|
+
# --> unless @base_path.nil?
|
50
|
+
# --> key_api.register_keystore( @base_path )
|
51
|
+
# --> end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
def pre_validation
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
|
2
|
+
# safe jenkins <command>
|
3
|
+
|
4
|
+
|
5
|
+
### safe jenkins post [aws|docker|git] <<jenkins-host-url>> | introduction
|
6
|
+
|
7
|
+
Use **`safe jenkins post`** to inject both your **AWS IAM User** and **docker login/password** credentials into your Jenkins 2.0 continuous integration portal reachable by the **jenkins host url** given in the 4th parameter of the safe command.
|
8
|
+
|
9
|
+
---
|
10
|
+
|
11
|
+
## safe jenkins post | prerequisite
|
12
|
+
|
13
|
+
Before you can inject credentials into jenkins using **`safe jenkins post`** you must
|
14
|
+
|
15
|
+
- be logged into your safe
|
16
|
+
- have opened the appropriate chapter/verse
|
17
|
+
- have put the required credential key/value pairs into the safe
|
18
|
+
- have the jenkins service up and running
|
19
|
+
|
20
|
+
After the post (to jenkins), your continuous integration jobs will be able to access the credential values via their IDs as stated in the below table.
|
21
|
+
|
22
|
+
---
|
23
|
+
|
24
|
+
## safe jenkins post aws | key names table
|
25
|
+
|
26
|
+
As credentials are WORO (write once, read often), safe makes the reading part very very easy (and secure) so your effort is frontloaded.
|
27
|
+
|
28
|
+
| Safe Key | Jenkins Credential IDs | Environment Variable | Description |
|
29
|
+
|:-----------:|:----------------------:|:--------------------- |:-------------------------------------------------------- |
|
30
|
+
| @access.key | safe.aws.access.key | AWS_ACCESS_KEY_ID | The AWS IAM user's access key credential. |
|
31
|
+
| @secret.key | safe.aws.secret.key | AWS_SECRET_ACCESS_KEY | The AWS IAM user's secret key credential. |
|
32
|
+
| region.key | safe.aws.region.key | AWS_REGION | The AWS region key that your Jenkins service points to. |
|
33
|
+
|
34
|
+
So you can see that by convention, safe expects the credential keys in the safe to be named a particular way, and likewise, you can be assured of the IDs it gives those credentials when posted to Jenkins.
|
35
|
+
|
36
|
+
|
37
|
+
## safe jenkins post | credentials lifecycle
|
38
|
+
|
39
|
+
The life of the credentials begins when you create an IAM user and record its access and secret keys. Then
|
40
|
+
|
41
|
+
- you login to safe and store the 3 keys and their values
|
42
|
+
- safe jenkins post will read the values and post them to Jenkins
|
43
|
+
- Jenkins stores the values in conjunction with the Jenkins Credential IDs
|
44
|
+
- pipeline jobs ask Jenkins to put the Credential ID values against environment variables
|
45
|
+
- tools like Terraform and AwsCli use the environment variables to work in the cloud
|
46
|
+
|
47
|
+
|
48
|
+
## Jenkinsfile | Usage in Pipeline Jobs
|
49
|
+
|
50
|
+
Here is a pipeline declaration within a Jenkinsfile that asks Jenkins to put the credential values in its secrets store into the stated environment variables.
|
51
|
+
|
52
|
+
environment
|
53
|
+
{
|
54
|
+
AWS_ACCESS_KEY_ID = credentials( 'safe.aws.access.key' )
|
55
|
+
AWS_SECRET_ACCESS_KEY = credentials( 'safe.aws.secret.key' )
|
56
|
+
AWS_REGION = credentials( 'safe.aws.region.key' )
|
57
|
+
}
|
58
|
+
|
59
|
+
After **`safe jenkins post aws`** you can **click into the Credentials item in the Jenkins main menu** to assure yourself that the credentials have indeed been properly injected.
|
60
|
+
|
61
|
+
---
|
62
|
+
|
63
|
+
## How to Write AWS Credentials into your Safe
|
64
|
+
|
65
|
+
In order to **`safe terraform apply`** or **`safe jenkins post aws <<jenkins-host-url>>`** or `safe visit` you must first put those ubiquitous IAM programmatic user credentials into your safe.
|
66
|
+
|
67
|
+
$ safe login joebloggs.com # open the book
|
68
|
+
|
69
|
+
$ safe open iam dev.s3.reader # open chapter and verse
|
70
|
+
$ safe put @access.key ABCD1234EFGH5678 # Put IAM access key in safe
|
71
|
+
$ safe put @secret.key xyzabcd1234efgh5678 # Put IAM secret key in safe
|
72
|
+
$ safe put region.key eu-west-3 # infrastructure in Paris
|
73
|
+
|
74
|
+
$ safe open iam canary.admin # open chapter and verse
|
75
|
+
$ safe put @access.key 4321DCBA8765WXYZ # Put IAM access key in safe
|
76
|
+
$ safe put @secret.key 5678uvwx4321abcd9876 # Put IAM secret key in safe
|
77
|
+
$ safe put region.key eu-west-1 # infrastructure in Dublin
|
78
|
+
|
79
|
+
$ safe logout
|
80
|
+
|
81
|
+
|
82
|
+
---
|
83
|
+
|
84
|
+
|
85
|
+
## How to write DockerHub Credentials into your Safe
|
86
|
+
|
87
|
+
#### safe jenkins post docker https://jenkins.example.com
|
88
|
+
|
89
|
+
Before you can issue a **`safe jenkins post docker http://localhost:8080`** you must insert your docker login credentials in the form of a username and @password into your safe. Remember that any key starting with the `@ sign` tells the safe to keep it a secret like when you issue a **`safe show`** command.
|
90
|
+
|
91
|
+
$ safe login joebloggs.com # open the book
|
92
|
+
$ safe open docker production # at the docker (for production) chapter and verse
|
93
|
+
$ safe put username admin # Put the Docker repository login username into the safe
|
94
|
+
$ safe put @password secret12345 # Put the Docker repository login @password into the safe
|
95
|
+
$ safe logout
|
96
|
+
|
97
|
+
When docker credentials are injected into a Jenkins service the safe will expect to find a key at the open chapter and verse called username and another one called password.
|
98
|
+
|
99
|
+
The safe promises to inject credentials with an ID of **safe.docker.login.id** so any jenkins jobs that need to use the docker login username and password must specify this ID when talking to the Jenkins credentials service.
|
100
|
+
|
101
|
+
|
102
|
+
### DockerHub Credentials Inject Response
|
103
|
+
|
104
|
+
Here is an example of posting dockerhub credentials into a Jenkins service running on the local machine.
|
105
|
+
|
106
|
+
``` bash
|
107
|
+
safe jenkins post docker http://localhost:8080
|
108
|
+
```
|
109
|
+
|
110
|
+
If successful safe provides a polite response detailing what just happened.
|
111
|
+
|
112
|
+
```
|
113
|
+
- Jenkins Host Url : http://localhost:8080/credentials/store/system/domain/_/createCredentials
|
114
|
+
- Credentials ID : safe.docker.login.id
|
115
|
+
- Inject Username : devops4me
|
116
|
+
- So what is this? : The docker repository login credentials in the shape of a username and password.
|
117
|
+
|
118
|
+
% Total % Received % Xferd Average Speed Time Time Time Current
|
119
|
+
Dload Upload Total Spent Left Speed
|
120
|
+
100 428 0 0 100 428 0 47555 --:--:-- --:--:-- --:--:-- 47555
|
121
|
+
```
|
122
|
+
|
123
|
+
---
|
124
|
+
|
125
|
+
|
126
|
+
## safe integrations | we need your help
|
127
|
+
|
128
|
+
**You can help to extend safe's integrations.**
|
129
|
+
|
130
|
+
By design - safe integrations are simple to write. They primarily integrate with producers and consumers. To deliver efficacy to devops engineers safe will endeavour to
|
131
|
+
|
132
|
+
- **send** credentials to **downstream consumers** and
|
133
|
+
- **receive** credentials from **upstream producers**
|
134
|
+
|
135
|
+
safe needs pull requests from the devops community and it promises to always strive to keep the task of writing an integration extremely simple.
|
136
|
+
|
137
|
+
### integrations | what giving takes?
|
138
|
+
|
139
|
+
Currently, writing an integration entails delivering 3 or 4 artifacts which are
|
140
|
+
|
141
|
+
- 1 simple Ruby class
|
142
|
+
- 1 README.md documenting the command structure, the prerequisites and the expected outcome
|
143
|
+
- 1 class containing unit tests
|
144
|
+
- (optionaly) an INI file if many configuration and facts are involved
|
145
|
+
|
146
|
+
Giving doesn't take much so roll up your sleeves (or frocks) and get writing.
|
@@ -0,0 +1,208 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
module SafeDb
|
4
|
+
|
5
|
+
# This Jenkins use case handles the to and fro integration of secrets and sensitive information
|
6
|
+
# between the safe database under management and a Jenkins service pinpointed by an incoming
|
7
|
+
# host url parameter.
|
8
|
+
#
|
9
|
+
# This Jenkins use case injects for example the AWS IAM user access key, secret key and region key
|
10
|
+
# into a running Jenkins CI (Continuous Integration) service at the specified (url) location.
|
11
|
+
#
|
12
|
+
# safe jenkins post <<[ aws | docker | git ]>> <<jenkins-host-url>>
|
13
|
+
|
14
|
+
class Jenkins < UseCase
|
15
|
+
|
16
|
+
# The three instance variables provided through the command line like
|
17
|
+
# for example $ safe jenkins post aws http://localhost:8080
|
18
|
+
# For more info visit the documentation in the command interpreter class.
|
19
|
+
attr_writer :command, :service, :url
|
20
|
+
|
21
|
+
# If string variables EXPLODE throughout (and come to dominate) this class
|
22
|
+
# we should consider introducing an INI factfile like the [vpn] use case.
|
23
|
+
JENKINS_URI_PATH = "credentials/store/system/domain/_/createCredentials"
|
24
|
+
|
25
|
+
# If string variables EXPLODE throughout (and come to dominate) this class
|
26
|
+
# we should consider introducing an INI factfile like the [vpn] use case.
|
27
|
+
SECRET_KEY_VALUE_PAIR_DICTIONARY =
|
28
|
+
{
|
29
|
+
"scope" => "GLOBAL",
|
30
|
+
"$class" => "org.jenkinsci.plugins.plaincredentials.impl.StringCredentialsImpl"
|
31
|
+
}
|
32
|
+
|
33
|
+
# If string variables EXPLODE throughout (and come to dominate) this class
|
34
|
+
# we should consider introducing an INI factfile like the [vpn] use case.
|
35
|
+
SECRET_KEY_VALUE_PAIR_TO_POST = { "" => "0", "credentials" => SECRET_KEY_VALUE_PAIR_DICTIONARY }
|
36
|
+
|
37
|
+
|
38
|
+
# If string variables EXPLODE throughout (and come to dominate) this class
|
39
|
+
# we should consider introducing an INI factfile like the [vpn] use case.
|
40
|
+
USERNAME_AND_PASSWORD_DICTIONARY =
|
41
|
+
{
|
42
|
+
"scope" => "GLOBAL",
|
43
|
+
"$class" => "com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl"
|
44
|
+
}
|
45
|
+
|
46
|
+
# If string variables EXPLODE throughout (and come to dominate) this class
|
47
|
+
# we should consider introducing an INI factfile like the [vpn] use case.
|
48
|
+
USERNAME_AND_PASSWORD_TO_POST = { "" => "0", "credentials" => USERNAME_AND_PASSWORD_DICTIONARY }
|
49
|
+
|
50
|
+
|
51
|
+
|
52
|
+
# Inject a Jenkins credential key-value pair that is secret and/or sensitive and
|
53
|
+
# needs to be referenced by executing continuous integration jobs.
|
54
|
+
#
|
55
|
+
# @param jenkins_base_url [String]
|
56
|
+
#
|
57
|
+
# This base url includes the scheme (protocol) which can be either http
|
58
|
+
# or https. It can include the port if it is not either 80 or 443. A common
|
59
|
+
# example is http://localhost:8080 but can also be https://jenkins.example.com
|
60
|
+
# It pays not to provide a trailing backslash on this url.
|
61
|
+
#
|
62
|
+
# @param credentials_id [String]
|
63
|
+
#
|
64
|
+
# The ID that Jenkins jobs will use to reference this credential's value.
|
65
|
+
#
|
66
|
+
# @param secret_value [String]
|
67
|
+
#
|
68
|
+
# The value of this credential (secret) that will be injected for SafeKeeping
|
69
|
+
# to the Jenkins service at the provided URL.
|
70
|
+
#
|
71
|
+
# @param description [String]
|
72
|
+
#
|
73
|
+
# Description of the credential that will be posted and can be viewed via
|
74
|
+
# the Jenkins user interface.
|
75
|
+
def inject_secret_key_value_pair( jenkins_base_url, credentials_id, secret_value, description )
|
76
|
+
|
77
|
+
jenkins_url = File.join( jenkins_base_url, JENKINS_URI_PATH )
|
78
|
+
|
79
|
+
credentials_dictionary = SECRET_KEY_VALUE_PAIR_DICTIONARY
|
80
|
+
credentials_dictionary.store( "id", credentials_id )
|
81
|
+
credentials_dictionary.store( "secret", secret_value )
|
82
|
+
credentials_dictionary.store( "description", description )
|
83
|
+
|
84
|
+
curl_cmd = "curl -X POST '#{jenkins_url}' --data-urlencode 'json=#{SECRET_KEY_VALUE_PAIR_TO_POST.to_json}'"
|
85
|
+
|
86
|
+
puts ""
|
87
|
+
puts " - Jenkins Host Url : #{jenkins_url}"
|
88
|
+
puts " - Credentials ID : #{credentials_id}"
|
89
|
+
puts " - So what is this? : #{description}"
|
90
|
+
puts ""
|
91
|
+
|
92
|
+
%x[ #{curl_cmd} ]
|
93
|
+
|
94
|
+
puts ""
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
|
100
|
+
# Inject into Jenkins a username and password pairing against an ID key that the
|
101
|
+
# continuous integration jobs know and can use to access the credentials pair.
|
102
|
+
#
|
103
|
+
# @param jenkins_base_url [String]
|
104
|
+
#
|
105
|
+
# This base url includes the scheme (protocol) which can be either http
|
106
|
+
# or https. It can include the port if it is not either 80 or 443. A common
|
107
|
+
# example is http://localhost:8080 but can also be https://jenkins.example.com
|
108
|
+
# It pays not to provide a trailing backslash on this url.
|
109
|
+
#
|
110
|
+
# @param credentials_id [String]
|
111
|
+
#
|
112
|
+
# The ID that Jenkins jobs will use to reference this credential's value.
|
113
|
+
#
|
114
|
+
# @param username [String]
|
115
|
+
#
|
116
|
+
# The value of this username (secret) that will be injected for SafeKeeping
|
117
|
+
# to the Jenkins service at the provided URL.
|
118
|
+
#
|
119
|
+
# @param password [String]
|
120
|
+
#
|
121
|
+
# The value of this password (secret) that will be injected for SafeKeeping
|
122
|
+
# to the Jenkins service at the provided URL.
|
123
|
+
#
|
124
|
+
# @param description [String]
|
125
|
+
#
|
126
|
+
# Description of the username and password pairing that will be posted and
|
127
|
+
# can be viewed via the Jenkins user interface.
|
128
|
+
def inject_username_and_password( jenkins_base_url, credentials_id, username, password, description )
|
129
|
+
|
130
|
+
jenkins_url = File.join( jenkins_base_url, JENKINS_URI_PATH )
|
131
|
+
|
132
|
+
credentials_dictionary = USERNAME_AND_PASSWORD_DICTIONARY
|
133
|
+
credentials_dictionary.store( "id", credentials_id )
|
134
|
+
credentials_dictionary.store( "username", username )
|
135
|
+
credentials_dictionary.store( "password", password )
|
136
|
+
credentials_dictionary.store( "description", description )
|
137
|
+
|
138
|
+
curl_cmd = "curl -X POST '#{jenkins_url}' --data-urlencode 'json=#{USERNAME_AND_PASSWORD_TO_POST.to_json}'"
|
139
|
+
|
140
|
+
puts ""
|
141
|
+
puts " - Jenkins Host Url : #{jenkins_url}"
|
142
|
+
puts " - Credentials ID : #{credentials_id}"
|
143
|
+
puts " - Inject Username : #{username}"
|
144
|
+
puts " - So what is this? : #{description}"
|
145
|
+
puts ""
|
146
|
+
|
147
|
+
%x[ #{curl_cmd} ]
|
148
|
+
|
149
|
+
puts ""
|
150
|
+
|
151
|
+
end
|
152
|
+
|
153
|
+
|
154
|
+
|
155
|
+
def execute
|
156
|
+
|
157
|
+
return unless ops_key_exists?
|
158
|
+
master_db = get_master_database()
|
159
|
+
return if unopened_envelope?( master_db )
|
160
|
+
|
161
|
+
# Get the open chapter identifier (id).
|
162
|
+
# Decide whether chapter already exists.
|
163
|
+
# Then get (or instantiate) the chapter's hash data structure
|
164
|
+
chapter_id = ENVELOPE_KEY_PREFIX + master_db[ ENV_PATH ]
|
165
|
+
verse_id = master_db[ KEY_PATH ]
|
166
|
+
chapter_exists = KeyApi.db_envelope_exists?( master_db[ chapter_id ] )
|
167
|
+
|
168
|
+
# Unlock the chapter data structure by supplying
|
169
|
+
# key/value mini-dictionary breadcrumbs sitting
|
170
|
+
# within the master database at the section labelled
|
171
|
+
# envelope@<<actual_chapter_id>>.
|
172
|
+
chapter_data = KeyDb.from_json( KeyApi.content_unlock( master_db[ chapter_id ] ) )
|
173
|
+
|
174
|
+
key_value_dictionary = chapter_data[ verse_id ]
|
175
|
+
|
176
|
+
inject_aws_credentials( key_value_dictionary ) if @service.eql?( "aws" )
|
177
|
+
inject_docker_credentials( key_value_dictionary ) if @service.eql?( "docker" )
|
178
|
+
|
179
|
+
end
|
180
|
+
|
181
|
+
|
182
|
+
|
183
|
+
def inject_aws_credentials( mini_dictionary )
|
184
|
+
|
185
|
+
access_key_desc = "The access key of the AWS IAM (programmatic) user credentials."
|
186
|
+
secret_key_desc = "The secret key of the AWS IAM (programmatic) user credentials."
|
187
|
+
region_key_desc = "The AWS region key for example eu-west-1 for Dublin in Ireland."
|
188
|
+
|
189
|
+
inject_secret_key_value_pair( @url, "safe.aws.access.key", mini_dictionary[ "@access.key" ], access_key_desc )
|
190
|
+
inject_secret_key_value_pair( @url, "safe.aws.secret.key", mini_dictionary[ "@secret.key" ], secret_key_desc )
|
191
|
+
inject_secret_key_value_pair( @url, "safe.aws.region.key", mini_dictionary[ "region.key" ], region_key_desc )
|
192
|
+
|
193
|
+
end
|
194
|
+
|
195
|
+
|
196
|
+
def inject_docker_credentials( mini_dictionary )
|
197
|
+
|
198
|
+
docker_desc = "The docker repository login credentials in the shape of a username and password."
|
199
|
+
|
200
|
+
inject_username_and_password( @url, "safe.docker.login.id", mini_dictionary[ "docker.username" ], mini_dictionary[ "@docker.password" ], docker_desc )
|
201
|
+
|
202
|
+
end
|
203
|
+
|
204
|
+
|
205
|
+
end
|
206
|
+
|
207
|
+
|
208
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
module SafeDb
|
4
|
+
|
5
|
+
# The <b>login use case</b> is given the domain name and if needs be
|
6
|
+
# it collects the password then (if correct) logs the user in.
|
7
|
+
#
|
8
|
+
# Here are some key facts about the login command
|
9
|
+
#
|
10
|
+
# - its domain name parameter is mandatory
|
11
|
+
# - it is called at the start of every session
|
12
|
+
# - it is undone by the logout command
|
13
|
+
# - it requires the shell token environment variable to be set
|
14
|
+
# - you can nest login commands thus using multiple domains
|
15
|
+
# - you can call it with a --with=password switch
|
16
|
+
# - a space before the command prevents it being logged in .bash_history
|
17
|
+
# - you can deliver the password in multiple ways
|
18
|
+
class Login < UseCase
|
19
|
+
|
20
|
+
attr_writer :master_p4ss, :domain_name
|
21
|
+
|
22
|
+
|
23
|
+
def execute
|
24
|
+
|
25
|
+
return unless ops_key_exists?
|
26
|
+
|
27
|
+
unless ( KeyApi.is_domain_keys_setup?( @domain_name ) )
|
28
|
+
print_not_initialized
|
29
|
+
return
|
30
|
+
end
|
31
|
+
|
32
|
+
############## Call [[ KeyApi.is_logged_in? ]] - then print msg and skip password collection below
|
33
|
+
############## Call [[ KeyApi.is_logged_in? ]] - then print msg and skip password collection below
|
34
|
+
############## Call [[ KeyApi.is_logged_in? ]] - then print msg and skip password collection below
|
35
|
+
############## Call [[ KeyApi.is_logged_in? ]] - then print msg and skip password collection below
|
36
|
+
############## Call [[ KeyApi.is_logged_in? ]] - then print msg and skip password collection below
|
37
|
+
############## Call [[ KeyApi.is_logged_in? ]] - then print msg and skip password collection below
|
38
|
+
|
39
|
+
domain_secret = KeyPass.password_from_shell( false )
|
40
|
+
|
41
|
+
############## Use [[ KeyApi.valid_password? ]] and give error if not valid
|
42
|
+
############## Use [[ KeyApi.valid_password? ]] and give error if not valid
|
43
|
+
############## Use [[ KeyApi.valid_password? ]] and give error if not valid
|
44
|
+
############## Use [[ KeyApi.valid_password? ]] and give error if not valid
|
45
|
+
############## Use [[ KeyApi.valid_password? ]] and give error if not valid
|
46
|
+
|
47
|
+
KeyApi.do_login( @domain_name, domain_secret, create_header() )
|
48
|
+
|
49
|
+
view_uc = View.new
|
50
|
+
view_uc.flow_of_events
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
# Perform pre-conditional validations in preparation to executing the main flow
|
56
|
+
# of events for this use case. This method may throw the below exceptions.
|
57
|
+
#
|
58
|
+
# @raise [SafeDirNotConfigured] if the safe's url has not been configured
|
59
|
+
# @raise [EmailAddrNotConfigured] if the email address has not been configured
|
60
|
+
# @raise [StoreUrlNotConfigured] if the crypt store url is not configured
|
61
|
+
def pre_validation
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
|